随着新的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)
每个项目都重复了,因为我在xf
语句中调用println
。
为什么每次重复两次项目?
为什么我没有?
在做出教育的时候我总能得到零吗?
我可以转发这种行为吗?
无论如何我做了
> (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
时将eduction
作为sequence
参数?
3)在nil
或eduction
时,我能否始终确定sequence
?
4)什么是eduction
什么是惯用的想法,它不可简化?或者如果是,那我怎么能减少呢?
5)为什么我在sequence
或eduction
时会出现副作用?
6)我可以使用换能器创建实际的延迟序列吗?
答案 0 :(得分:20)
很多问题,让我们首先从几个答案开始:
xf
== xform
是"传感器"。my-identity
函数无法编译。你有一个参数然后
该函数的多个其他元素。我相信你忘记了(fn ...)
。您对身份传感器的论证称为xf
。但是,这是
通常称为rf
,这意味着"减少功能"。现在令人困惑的部分是
xf
' s也在减少函数(因此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使用变异,你会得到重复
即使你没有正确捕捉你第一次打电话的结果
您的arity 2函数中的(rf)
(作为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
回答你的问题:
eduction
当它成为nil
参数时,它并没有真正通过result
的减少即可。打印时只调用Iterable
接口nil
真的来自TransformerIterator
,这是一个特殊的类
为传感器而创建。正如您所注意到的,此类也用于sequence
。
正如文档所述:逐步计算得到的序列元素。这些序列 将根据需要逐步消耗输入并完全实现中间 操作。此行为与lazy上的等效操作不同 序列。
您收到nil
作为result
参数的原因是因为迭代器没有结果集合,它保存了到目前为止迭代的元素。它只是遍及每个元素。没有任何州正在积累。
您可以在此处查看TransformerIterator
as和内部类使用的reduce函数:
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 [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))))