12

有了新的 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]我从文档示例中获取了变量的命名。我认为这就像在减少功能中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)

xf因为我在println声明中调用,所以每个项目都重复了。为什么它将每个项目重复两次?为什么我是零?进行推理时我总是会得到 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论点 while eductionor sequence

3)我可以始终确定它会是nilwhileeduction还是sequence

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

sequence5) 为什么我在or时会出现副作用eduction

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

4

1 回答 1

25

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

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

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

  3. 您对身份转换器的论点称为xf。但是,这通常称为rf,意思是“减少功能”。现在令人困惑的部分是xf's 也在减少功能(因此comp可以正常工作)。但是,令人困惑的是,您会调用它xf并且应该调用它rf

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

  5. 让我们首先继续并假设您的my-identity传感器是正确的(事实并非如此,稍后我将解释发生了什么)。

  6. eduction比较少用。它创建了一个“过程”。也就是说,您可以一遍又一遍地运行它并查看结果。基本上,就像您有保存项目的列表或向量一样,推导将“保存”应用的传感器的结果。请注意,要实际做任何事情,您仍然需要一个rf(减少功能)。

  7. 一开始,我认为将减少功能视为conj(或实际上conj!)或在您的情况下是有帮助的+

  8. eduction打印它产生的元素,因为它实现Iterable 了由 theprintln或您的 REPL 调用的元素。它只是打印出您通过 arity 2 调用添加到传感器中的每个元素。

  9. 您的调用(reduce + (eduction my-identity (range 5)))不起作用,因为Eduction(在 中构造的对象eduction)仅实现 IReduceInit. IReduceInit顾名思义,它确实需要一个初始值。所以这将起作用:(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))))

笔记:

  • 我重命名xfrf如前所述。
  • 现在我们可以看到您使用了您的结果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作为论点传递。 它仅在打印调用接口时才为零。resultIterable
  2. 真正的nil来源TransformerIterator是为传感器创建的特殊类。sequence如您所见,此类也用于。正如文档所述:

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

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

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

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

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

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

Clojure 转换器是否渴望?

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))))
于 2015-08-13T15:20:51.430 回答