使用新的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))
是至少在这样小的例子中,在没有任何状态更改器(mutator)的情况下,总是等于。他们不是,这是愚蠢的,还是我错了?
好吧,然后我尝试了
(type (sequence my-identity (range 5)))
并得到了clojure.lang.LazySeq
我以为这很懒,但是当我尝试使用
first
元素时clojure一次计算了所有序列。
所以我的总结:
1)什么是xf或xform?
2)为什么在
nil
或result
时将eduction
作为sequence
参数?3)我是否总能确定它是
nil
还是eduction
?4)什么是
sequence
?什么是惯用的想法?或如果是的话,我该如何减少呢?5)为什么在
eduction
或sequence
时出现副作用?6)我可以用换能器创建实际的延迟序列吗?
最佳答案
很多问题,让我们首先从一些答案开始:
xf
== xform
是“转换器”。my-identity
函数无法编译。您有一个参数,然后功能的其他多个方面。我相信您忘记了
(fn ...)
。xf
。但是,这是通常称为
rf
,表示“缩减功能”。现在令人困惑的部分是xf
的功能也有所减少(因此comp
可以正常工作)。然而,您将其称为
xf
并应将其称为rf
令人困惑。传递的参数。就您而言,由于它是
简单,没有状态,甚至没有参数。但是请注意,您会
通常将您的函数包装在另一个
fn
返回函数中。这意味着你会必须调用
(my-identity)
而不是仅将其作为my-identity
传递。再次,这很好,只是有点不合常规,并且可能造成混淆。
my-identity
转换器是正确(不是,我稍后会解释发生了什么)。
eduction
相对很少使用。它创建一个“过程”。IE。您可以一遍又一遍地运行它并查看结果。基本上,只是
就像您有列表或 bootstrap 来保存您的项目时,教育将“保留”
换能器的应用结果。请注意,实际上您可以做任何事情
仍然需要一个
rf
(归约函数)。作为
conj
(或实际上conj!
)或您的情况+
。eduction
,所以Iterable
会打印其生成的元素由
println
或您的REPL调用。它只是打印出每个您通过arity 2调用添加到传感器中的元素。
(reduce + (eduction my-identity (range 5)))
的调用不起作用因为
Eduction
(在eduction
中构造的对象)仅实现IReduceInit
。 IReduceInit
的名称表明是否需要初始值(value)。这样就可以了:
(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
参数传递减少了。它只有在打印时才为nil,它调用
Iterable
界面。 nil
确实来自TransformerIterator
,这是一个特殊的类为换能器创建。正如您所注意到的,此类也用于
sequence
。如文档所述:
之所以将
nil
作为result
参数接收,是因为迭代器没有结果集合,该集合无法保存到目前为止已迭代的元素。它只是遍历每个元素。没有任何状态正在累积。您可以在此处看到
TransformerIterator
和内部类使用的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 [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))))
关于Clojure换能器行为,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/31986435/