【问题标题】:Clojure transducers behaviorClojure 传感器行为
【发布时间】:2015-11-06 07:33:46
【问题描述】:

有了新的 clojure 1.7,我决定了解在哪里可以使用传感器。我了解它们可以带来什么好处,但我找不到编写自定义转换器并附有解释的常规示例。

好的,我尝试测试发生了什么。我打开了 clojure 文档。还有一些例子使用xf 作为参数。第一:这个 xf 或 xfrom 是什么意思? 这些东西产生了身份传感器。

(defn my-identity [xf]
  (fn 
   ([]
     (println "Arity 0.")
     (xf))
   ([result]
     (println "Arity 1: " result " = " (xf result)) 
     (xf result))
   ([result input]
     (println "Arity 2: " result input " = " (xf result input)) 
     (xf result input))))

我从文档示例中获取了变量 [result input] 的命名。 我认为这就像在 reduce 函数中一样,result 是缩减部分,input 是新的集合元素。

所以当我创建(transduce my-identity + (range 5)) 时,我得到了10 所期望的结果。 然后我读到了eduction,但我不明白它是什么。反正 我发了(eduction my-identity (range 5)) 并得到:

Arity 2:  nil 0  =  nil
Arity 2:  nil 1  =  nil
Arity 1: nil  =  nil
(0 0 1 1)

每个项目都重复了,因为我在 println 语句中调用了 xf。 为什么它将每个项目重复两次? 为什么我是零? 进行推理时我总是会得到 nil 吗? 我可以转述这种行为吗?

反正我是这样做的

> (reduce + (eduction my-identity (range 5))
clojure.core.Eduction cannot be cast to clojure.lang.IReduce

好的,结果是一个Eduction,它是不可约的,但像一个列表一样打印出来。为什么不能还原?当我输入(doc eduction) 时,我明白了

Returns a reducible/iterable application of the transducers
to the items in coll.

(transduce xform f coll)(reduce f (eduction xfrom coll)) 不应该是一样的吗?

我做了

> (reduce + (sequence my-identity (range 5))
20

当然,因为重复,我得到了20。我再次认为它应该是 (transduce xform f coll)(reduce f (sequence xfrom coll)) 是 在没有任何有状态转换器的情况下,至少在这样的小例子中总是相等的。他们不这样做是愚蠢的,还是我错了?

好的,然后我尝试(type (sequence my-identity (range 5))) 并得到 clojure.lang.LazySeq 我想,这很懒,但是当我尝试使用 first 元素时 clojure 一次计算所有序列。

所以我的总结:

1) xf 或 xform 是什么意思?

2) 为什么我得到nil 作为result 参数,而eductionsequence

3) 我能否始终确定它是nileductionsequence

4) 什么是eduction,它不可还原的惯用想法是什么?或者如果是,那我该如何减少呢?

5) 为什么我在使用sequenceeduction 时会出现副作用?

6) 我可以使用传感器创建实际的惰性序列吗?

【问题讨论】:

  • 1) clojure.org/transducers - "传感器 xf 是一个转换堆栈...";所以,xf 是一个转换器(或 xfs 的组合),它不是一个函数 (f),所以将它命名为 xf
  • 1) 编辑:应该将其表述为'并将其与常规函数区分开来(f)将其命名为xf'
  • 你在这里有非常有趣的问题,但我认为如果你从中提取更小的问题,你会得到更多更好的答案。问“1”可以是一个单独的问题(“xf 或 xform 在转换器上下文中的含义是什么?”),它甚至不需要示例,同样适用于问题 4 和 6。

标签: clojure transducer


【解决方案1】:

很多问题,我们先从几个答案开始:

  1. 是的,xf==xform 是一个“转换器”。

  2. 您的my-identity 函数无法编译。你有一个参数,然后 该函数的多个其他元素。我相信你忘记了(fn ...)

  3. 您对身份转换器的论点称为xf。然而,这是 通常称为rf,意思是“减函数”。现在令人困惑的部分是 xf 也正在减少功能(因此 comp 可以正常工作)。然而, 令人困惑的是,您将其称为 xf,而您应该将其称为 rf

  4. 转换器通常是“构造的”,因为它们可能是有状态的和/或 传递的参数。在您的情况下,您不需要构建它,因为它是 简单,没有状态,甚至没有参数。但是请注意,您会 通常将您的函数包装在另一个 fn 返回函数中。这意味着你会 必须调用(my-identity) 而不是将其传递为my-identity。 再说一次,这里没问题,只是有点不合常规,可能会令人困惑。

  5. 让我们首先继续并假设您的my-identity 转换器是 正确(不是,我稍后会解释发生了什么)。

  6. eduction 相对很少使用。它创建了一个“过程”。 IE。您可以一遍又一遍地运行它并查看结果。基本上,只要 就像您有包含您的项目的列表或向量一样,教育将“持有” 应用换能器的结果。请注意,要实际做任何事情 仍然需要rf(减少功能)。

  7. 一开始我认为考虑减少函数是有帮助的 作为conj(或者实际上是conj!)或者在你的情况下+

  8. 你的eduction 打印它产生的元素,因为它实现了Iterableprintln 或您的 REPL 调用。它只是打印出每个 使用 arity 2 调用添加到换能器中的元素。

  9. 您拨打(reduce + (eduction my-identity (range 5))) 打不通 因为Eduction(在eduction中构造的对象)只实现 IReduceInitIReduceInit 顾名思义是否需要首字母 价值。所以这会起作用:(reduce + 0 (eduction my-identity (range 5)))

  10. 现在,如果您按照我的建议运行上述reduce,您会看到一些非常 有趣的。它打印 10。即使您之前的导出打印了 (0 0 1 1 2 2 3 3 4 4)(如果加起来是 20)。这是怎么回事?

  11. 如前所述,您的传感器存在缺陷。它不能正常工作。这 问题是你打电话给你的rf 然后在你的 arity 2 函数。在 clojure 中,东西是不可变的,除非它以某种方式 内部可变以用于优化目的:)。 这里的问题是有时clojure使用突变并且你得到重复 即使您从未正确捕获第一次调用的结果 (rf) 在您的 arity 2 函数中(作为您的 println 的参数)。

让我们修复您的功能但将第二个 rf 呼叫留在那里

  (defn my-identity2 [rf]
    (fn
      ([]
       (println "Arity 0.")
       (rf))
      ([result]
       {:post [(do (println "Arity 1 " %) true)]
        :pre  [(do (println "Arity 1 " result) true)]}
       (rf result))
      ([result input]
       {:post [(do (println "Arity 2 " %) true)]
        :pre  [(do (println "Arity 2 " result input) true)]}
       (rf (rf result input) input))))

注意:

  • 如前所述,我将 xf 重命名为 rf
  • 现在我们可以看到您使用了rf 的结果并将其传递给 第二次拨打rf此转换器不是身份转换器,而是 将每个元素加倍

仔细观察:

(transduce my-identity + (range 5));; => 10
(transduce my-identity2 + (range 5));; => 20

(count (into '() my-identity (range 200)));; => 200
(count (into  [] my-identity (range 200)));; => 400

(count (into '() my-identity2 (range 200)));; => 400
(count (into  [] my-identity2 (range 200)));; => 400

(eduction my-identity  (range 5));;=> (0 0 1 1 2 2 3 3 4 4)
(eduction my-identity2 (range 5));;=> (0 0 1 1 2 2 3 3 4 4)

(into '() my-identity  (range 5));;=> (4 3 2 1 0)
(into  [] my-identity  (range 5));;=> [0 0 1 1 2 2 3 3 4 4]
(into '() my-identity2 (range 5));;=> (4 4 3 3 2 2 1 1 0 0)


(reduce + 0 (eduction my-identity (range 5)));;=> 10
(reduce +   (sequence my-identity (range 5)));;=> 20

(reduce + 0 (eduction my-identity2 (range 5)));;=> 20
(reduce +   (sequence my-identity2 (range 5)));;=> 20

回答您的问题:

  1. eduction 并没有真正将 nil 作为 result 参数传递给它 减少。它只有在打印时才为零,它调用Iterable 界面。
  2. nil 真的来自TransformerIterator 这是一个特殊的类 为传感器创建。如您所见,此类也用于sequence。 正如文档所述:

结果序列元素是增量计算的。这些序列 将根据需要增量消耗输入并完全实现中间 操作。此行为不同于惰性上的等效操作 序列。

您收到nil 作为result 参数的原因是因为迭代器没有生成的集合来保存迄今为止迭代过的元素。它只是遍历每个元素。没有状态被累积。

您可以在此处查看TransformerIterator as 和内部类使用的归约函数:

https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/TransformerIterator.java

执行CTRL+f 并输入xf.invoke 以查看您的传感器是如何被调用的。

sequence 函数并不像真正的惰性序列那样惰性,但我 认为这解释了你的这部分问题:

Are Clojure transducers eager?

sequence 只是以增量方式计算传感器的结果。没有 否则。

最后,带有一些调试语句的正确标识函数:

(defn my-identity-prop [rf]
  (fn
    ([]
     (println "Arity 0.")
     (rf))
    ([result]
     (let [r (rf result)]
       (println "my-identity(" result ") =" r)
       r))
    ([result input]
     (let [r (rf result input)]
       (println "my-idenity(" result "," input ") =" r)
       r))))

【讨论】:

  • 谢谢你这么好的回复。我现在明白那里发生了什么。 2) 是的,我忘记了 (fn ...),所以如果你不介意,我会编辑我的帖子来修复它。 3) 我从clojure.org/transducers dedupe 示例中取了名称“xf”。但是我查看了 clojure.core 的来源,它在那里被命名为“rf”,就像你建议的那样,它更有意义。最后我应该在这里提到“序列”按块计算序列,我的情况是32。这就是为什么我在(范围5)的测试中没有看到它。非常感谢。
  • 这是一个很好的答案。很遗憾,我只能投一票。
  • 哇,我从您的回答中学到了很多。顺便说一句,你为什么在正确的身份函数中称你为 arg xf 而不是 rf?
猜你喜欢
  • 2021-04-21
  • 1970-01-01
  • 1970-01-01
  • 2014-11-17
  • 1970-01-01
  • 2014-11-14
  • 1970-01-01
  • 1970-01-01
  • 2020-09-04
相关资源
最近更新 更多