使用新的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)为什么在nilresult时将eduction作为sequence参数?

3)我是否总能确定它是nil还是eduction

4)什么是sequence?什么是惯用的想法?或如果是的话,我该如何减少呢?

5)为什么在eductionsequence时出现副作用?

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中构造的对象)仅实现IReduceInitIReduceInit的名称表明是否需要初始
    值(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/

    10-11 00:04