Clojure传感器的行为

Jus*_*ous 11 clojure transducer

随着新的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))))
Run Code Online (Sandbox Code Playgroud)

[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)
Run Code Online (Sandbox Code Playgroud)

每个项目都重复,因为我xfprintln声明中打电话.为什么每次重复两次项目?为什么我没有?在做出教育的时候我总能得到零吗?我可以转发这种行为吗?

无论如何,我做到了

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

好的,结果是Eduction不可简化的,但是像列表一样打印.为什么它不可还原?当我输入时,(doc eduction)我得到了

Returns a reducible/iterable application of the transducers
to the items in coll.
Run Code Online (Sandbox Code Playgroud)

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

我做了

> (reduce + (sequence my-identity (range 5))
20
Run Code Online (Sandbox Code Playgroud)

当然,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)为什么我得到nilresult参数,同时eduction还是sequence

3)我能不能确定它会在什么nil时候eductionsequence

4)什么是eduction以及什么是不可简化的惯用理念?或者如果是,那我怎么能减少呢?

5)为什么我会在sequence或时产生副作用eduction

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

Clo*_*tly 20

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

  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相对很少使用.它创造了一个"过程".即你可以一遍又一遍地运行它并看到结果.基本上,就像你有列表或矢量来保存你的项目一样,教育将"保持"所应用传感器的结果.请注意,要实际执行任何操作,您仍需要rf(减少功能).

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

  8. eduction打印它实现的元素,因为它实现了Iterableprintln您或您的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))))
Run Code Online (Sandbox Code Playgroud)

注意:

  • 我改名xf,以rf作为注意earier.
  • 现在我们可以看到你使用你的结果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
Run Code Online (Sandbox Code Playgroud)

要回答你的问题:

  1. eduction当它被减少时,它并没有真正nil作为result论据 传递.它只在打印时调用 界面为零.Iterable
  2. nil真正来自TransformerIterator这对传感器创建了一个特殊类.这个类也用于sequence你注意到的.正如文档所述:

逐步计算得到的序列元素.这些序列将根据需要逐步消耗输入并完全实现中间操作.此行为与惰性序列上的等效操作不同.

nil作为result参数接收的原因是因为迭代器没有结果集合,该集合保存到目前为止迭代的元素.它只是遍及每个元素.没有任何州正在积累.

你可以在这里看到TransformerIteratoras和inner类使用的reduce函数:

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

做一个CTRL+f并进入xf.invoke以查看您的换能器是如何被调用的.

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

Clojure传感器是否渴望?

sequence简单地逐步计算换能器的结果.没有其他的.

最后,使用一些调试语句的正确身份函数:

(defn my-identity-prop [xf]
  (fn
    ([]
     (println "Arity 0.")
     (xf))
    ([result]
     (let [r (xf result)]
       (println "my-identity(" result ") =" r)
       r))
    ([result input]
     (let [r (xf result input)]
       (println "my-idenity(" result "," input ") =" r)
       r))))
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很好的答案.我唯一遗憾的是,我只有一票可以给予. (2认同)