Clojure传感器的行为

时间:2015-08-13 11:03:49

标签: 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))))

我从文档示例中获取了变量[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)为什么我在nilresult时将eduction作为sequence参数?

3)在nileduction时,我能否始终确定sequence

4)什么是eduction什么是惯用的想法,它不可简化?或者如果是,那我怎么能减少呢?

5)为什么我在sequenceeduction时会出现副作用?

6)我可以使用换能器创建实际的延迟序列吗?

1 个答案:

答案 0 :(得分:20)

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

  1. 是的,xf == xform是"传感器"。
  2. 您的my-identity函数无法编译。你有一个参数然后 该函数的多个其他元素。我相信你忘记了(fn ...)
  3. 您对身份传感器的论证称为xf。但是,这是 通常称为rf,这意味着"减少功能"。现在令人困惑的部分是 xf' s也在减少函数(因此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中构造的对象)只实现 IReduceInitIReduceInit正如其名称所暗示需要一个首字母 值。这样就行了:(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使用变异,你会得到重复 即使你没有正确捕捉你第一次打电话的结果 您的arity 2函数中的(rf)(作为println的参数)。

  12. 让我们修复你的功能,但在那里留下第二个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
    

    回答你的问题:

    1. eduction当它成为nil参数时,它并没有真正通过result减少即可。打印时只调用Iterable 接口
    2. nil真的来自TransformerIterator,这是一个特殊的类 为传感器而创建。正如您所注意到的,此类也用于sequence。 正如文档所述:
    3.   

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

      您收到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))))