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)
每个项目都重复,因为我xf
在println
声明中打电话.为什么每次重复两次项目?为什么我没有?在做出教育的时候我总能得到零吗?我可以转发这种行为吗?
无论如何,我做到了
> (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)为什么我得到nil
的result
参数,同时eduction
还是sequence
?
3)我能不能确定它会在什么nil
时候eduction
或sequence
?
4)什么是eduction
以及什么是不可简化的惯用理念?或者如果是,那我怎么能减少呢?
5)为什么我会在sequence
或时产生副作用eduction
?
6)我可以用传感器创建实际的延迟序列吗?
Clo*_*tly 20
很多问题,让我们首先从几个答案开始:
xf
== xform
是一个"换能器".my-identity
函数无法编译.你有一个参数,然后是函数的其他多个元素.我相信你忘记了(fn ...)
.您的身份传感器的参数被称为xf
.然而,这通常被称为rf
"减少功能".现在令人困惑的部分是xf
减少功能(因此comp
只是起作用).但是,你打电话给它是很困惑的,xf
你应该打电话给它rf
.
传感器通常是"构造的",因为它们可能是有状态的和/或被传递的参数.在您的情况下,您不需要构造它,因为它很简单,没有状态甚至参数.但请注意,您通常将函数包装在另一个fn
返回函数中.这意味着您必须调用(my-identity)
而不是仅仅将其传递给my-identity
.再次,这里很好,只是略微不稳定,可能令人困惑.
让我们先继续并假装你的my-identity
传感器是正确的(它不是,我会在后面解释发生了什么).
eduction
相对很少使用.它创造了一个"过程".即你可以一遍又一遍地运行它并看到结果.基本上,就像你有列表或矢量来保存你的项目一样,教育将"保持"所应用传感器的结果.请注意,要实际执行任何操作,您仍需要rf
(减少功能).
在开始时,我认为考虑减少功能conj
(或实际conj!
)或在您的情况下是有帮助的+
.
您eduction
打印它实现的元素,因为它实现了Iterable
由println
您或您的REPL 调用的元素.它只是通过arity 2调用打印出你在换能器中添加的每个元素.
你的调用(reduce + (eduction my-identity (range 5)))
不起作用,因为Eduction
(正在构造的对象eduction
)只实现
IReduceInit
.IReduceInit
顾名思义,确实需要一个初始值.所以这将有效:(reduce + 0 (eduction my-identity (range 5)))
现在,如果你reduce
按照我的建议运行上面的内容,你会看到非常有趣的东西.它打印10.即使您的早期打印出来(0 0 1 1 2 2 3 3 4 4)
(如果你加在一起是20).这里发生了什么?
如前所述,您的传感器有缺陷.它无法正常工作.问题是你打电话给你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)
要回答你的问题:
eduction
当它被减少时,它并没有真正nil
作为result
论据
传递.它只在打印时调用
界面为零.Iterable
nil
真正来自TransformerIterator
这对传感器创建了一个特殊类.这个类也用于sequence
你注意到的.正如文档所述:逐步计算得到的序列元素.这些序列将根据需要逐步消耗输入并完全实现中间操作.此行为与惰性序列上的等效操作不同.
您nil
作为result
参数接收的原因是因为迭代器没有结果集合,该集合保存到目前为止迭代的元素.它只是遍及每个元素.没有任何州正在积累.
你可以在这里看到TransformerIterator
as和inner类使用的reduce函数:
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/TransformerIterator.java
做一个CTRL+f
并进入xf.invoke
以查看您的换能器是如何被调用的.
这个sequence
函数并不像真正的懒惰序列那么懒,但我认为这解释了这部分问题:
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)