Clojure换能器行为 [英] Clojure transducers behavior

查看:162
本文介绍了Clojure换能器行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有了新的clojure 1.7,我决定了解在哪里可以使用换能器。我明白他们可以给予什么好处,但是我找不到正确的例子来说明自定义传感器的解释。

好吧,我试着测试发生了什么。我打开clojure文档。并且有例子使用 xf 作为参数。第一:什么意思是这个xf或xfrom?
这个东西产生了身份传感器。

 (defn my-identity [xf] 
(fn
([]
(printlnArity 0)
(xf))
([result]
(printlnArity 1:result= (xf结果))
(xf结果))
(结果输入)
(printlnArity 2:result input=(xf result input))
输入)))

我命名变量 从文档示例。
我认为这是在reduce函数中 result 是缩小部分,输入是新的收集元素。



因此,当我使(转换我的身份+(范围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)

每个项目都有重复,因为我在<$ c $中调用 xf c> println 语句。
为什么它重复每个项目两次?
为什么我有nil?
在进行教学时,我总是得到零?
我可以继续这种行为吗?



无论如何

 > (reduce +(eduction my-identity(range 5))
clojure.core.Eduction不能转换为clojure.lang.IReduce

好的,结果是一个 Eduction ,这是不可缩减的,但打印像列表为什么它不能还原? (doc eduction)我得到

 返回一个可缩减/的换能器
到合并中的项目

不应 是相同的吗?


$($) b $ b

我制作了

 >(reduce +(sequence my-identity(range 5))
20

当然,我得到 20 再次我认为应该是
(transduce xform f coll)(reduce f(sequence xfrom coll)) be
总是相等的,至少在这样小的例子没有任何有状态的换能器。这是愚蠢的,他们不是,或我错了吗?



好,然后我尝试(type(sequence my-identity(range 5)))并获得
clojure.lang.LazySeq
想到,它是懒惰,但当我试图采取第一元素
clojure计算所有的序列一次。



所以我的总结:



1)什么意思xf或xform?



2)为什么我获得 nil 作为结果 eduction 或序列



3)我可以始终确保 nil eduction 序列



4)什么是 eduction 和什么是惯用的想法是不可还原的?



5)为什么我会得到副作用而序列 eduction



6)我可以用传感器创建实际的延迟序列吗?

解决方案

很多问题,让我们先从几个角度开始:


  1. xf == xform 是一个传感器。

  2. code> my-identity 函数不编译。你有一个参数,然后
    多个其他arity的函数。我相信你忘了一个(fn ...)

  3. $ c> xf 。但是,这是
    通常称为 rf ,这意味着减少功能。现在令人困惑的部分是
    xf 的也是减少函数(因此 comp 只是工作)。然而,
    它是混乱,你会叫它 xf ,你应该调用它 rf 。 p>


  4. 传感器通常是构造的,因为它们可能是有状态的和/或是
    传递的参数。在你的case,你不需要构造它,因为它
    简单,没有状态,甚至一个参数。但是请注意,
    通常将函数包装在另一个 fn 返回函数中。这意味着你
    必须调用(my-identity),而不是将它作为 my-identity


  5. 让我们先继续假装你的 my-身份传感器是
    正确的(不是,我稍后会解释发生了什么。)


  6. eduction 是相对很少使用。它创建一个进程。
    即你可以一遍又一遍地运行它并查看结果。基本上,只是
    像你有列表或向量,持有您的项目,教育将保持
    应用的换能器的结果。注意,实际做任何事情你
    仍然需要一个 rf (减少函数)。


  7. 在开始我认为有用的是减少函数
    as conj (或实际上 conj!


  8. 您的 eduction 打印其生成的元素,因为它实现了 println 调用的 Iterable
    您的REPL。


  9. 您调用

    因为 Eduction (构造在中的对象) code> eduction )只实现
    IReduceInit IReduceInit ,因为其名称建议 require 初始
    值。因此,这将工作:(reduce + 0(eduction my-identity(range 5)))


  10. 现在,如果你运行上面的 reduce ,我建议你会看到一些非常有趣的
    。即使您早前打印的(0 0 1 1 2 2 3 3 4 4)(如果您添加在一起是20)。这是怎么回事?


  11. 如前所述,你的传感器有一个缺陷。它不能正常工作。
    的问题是你调用你的 rf ,然后再次在你的
    arity 2函数中调用它。在clojure,东西是不可变的,除非它以某种方式
    内部可变的优化目的:)。
    这里的问题是,有时clojure使用突变,你得到重复
    ,即使你从来没有正确捕获你第一次调用
    (rf)在您的arity 2函数中(作为 println 的参数)。


让我们修复你的功能但留下第二个 rf 调用

 (defn my-identity2 [rf] 
(fn
([]
(printlnArity 0)
(rf))
([result]
{:post [(do(printlnArity 1%)true)]
:pre [(do(printlnArity 1 resulttrue)]}
(rf result))
([result input]
{:post [(do(printlnArity 2%)true)]
:pre [(do(printlnArity 2result input)true)]}
(rf(rf result input)))))

注意:




  • 我重命名为 xf <$ c>到 rf rf ,并将其传递给
    第二次调用 rf 此换能器不是身份传感器,但
    将每个元素加倍



p>

 (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 / code>参数,当它是
    减少。它只有在打印时调用 Iterable
    接口。

  2. 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 以查看您的传感器是如何调用的。



序列函数并不像真正的延迟序列那么懒惰,但是我
认为这解释了你的这一部分问题: / p>

Clojure传感器是否渴望? (不延迟)



sequence 只是逐步计算传感器的结果。没有
其他。



最后,一个正确的身份函数与一些调试语句:

 (defn my-identity-prop [xf] 
(fn
([]
(printlnArity 0)
(xf))
([result]
(let [r(xf result)]
(printlnmy-identity(result)=r)
r))
([result input]
(let [r(xf result input)]
(printlnmy-idenity(result,input)=r)
r)


With new clojure 1.7 I decided to understand where I can use transducers. I understand what benefit they can give, but I can't find normal examples of writing custom transducers with explanation.

Ok, I tried to test what is happening. I opened the clojure documentation. And there examples use xf as argument. First: what means this xf or xfrom? This stuff produced identity transducer.

(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))))

I took the naming of variables [result input] from documentation example. I thought it's like in reduce function where result is reduced part and input is new collection element.

So when I make (transduce my-identity + (range 5)) I got result 10 what I was expecting. Then I read about eduction, but I can't understand what is it. Anyway I made (eduction my-identity (range 5)) and got:

Arity 2:  nil 0  =  nil
Arity 2:  nil 1  =  nil
Arity 1: nil  =  nil
(0 0 1 1)

Every item got duplicated because I call xf in println statement. Why it duplicated every item twice? Why I got nil? Will I always get nil while making an eduction? Can I relay on this behavior?

Anyway I did

> (reduce + (eduction my-identity (range 5))
clojure.core.Eduction cannot be cast to clojure.lang.IReduce

Ok, the result is a Eduction that is NOT reducible, but printed like a list. Why it is not reducible? When I type (doc eduction) I get that

Returns a reducible/iterable application of the transducers
to the items in coll.

Shouldn't (transduce xform f coll) and (reduce f (eduction xfrom coll)) be the same?

I made

> (reduce + (sequence my-identity (range 5))
20

Of course I got 20 because of duplicates. Again I thought it should be that (transduce xform f coll) and (reduce f (sequence xfrom coll)) be always equal at least in such small example without any stateful transducers. This is stupid that they are not, or I'm wrong?

Ok, then I tried (type (sequence my-identity (range 5))) and get clojure.lang.LazySeq I thought, that it's lazy but when I tried to take the first element clojure calculated all the sequence at once.

So my summary:

1) What means xf or xform?

2) Why I get nil as a result argument while eduction or sequence?

3) Could I always be sure that it will be nil while eduction or sequence?

4) What is eduction and what is the idiomatic idea it's not reducible? Or if it is, then how I can reduce it?

5) Why I get side effects while sequence or eduction?

6) Can I create actual lazy sequences with transducers?

解决方案

Many questions, let's first start with a few anwers:

  1. Yes, xf==xform is a "transducer".
  2. Your my-identity function does not compile. You have a parameter and then multiple other arities of the function. I believe you forgot a (fn ...).
  3. Your argument to your identity transducer is called xf. However, this is usually called rf, which means "reducing function". Now the confusing part is that xf's are also reducing functions (hence comp just works). However, it's confusing that you'd call it xf and you should call it rf.

  4. Transducers are usually "constructed" since they may be stateful and/or are passed parameters. In your case, you don't need to construct it since it's simple and doesn't have state or even a parameter. However be aware that you'd usually wrap your function in another fn returning function. This means you'd have to call (my-identity) instead of just passing it as my-identity. Again, it's fine here, just slightly unconvential and possibly confusing.

  5. Let's first continue and pretend that your my-identity transducer is correct (it's not, and I'll explain later what's going on).

  6. eduction is relatively rarely used. It creates a "process". I.e. you can run it over and over again and see the result. Basically, just like you have lists or vectors that hold your items the eduction will "hold" the result of the transducer applied. Note that to actually do anything you still need a rf (reducing function).

  7. In the beginning I think it is helpful to think of reducing functions as conj (or actually conj!) or in your case +.

  8. Your eduction prints the elements it produces since it implements Iterable which is called by the println or your REPL. It simply prints out every element that you add in you transducer with the arity 2 call.

  9. Your call to (reduce + (eduction my-identity (range 5))) doesn't work since Eduction (the object being constructed in eduction) only implements IReduceInit. IReduceInit as its name suggest does require an initial value. So this will work: (reduce + 0 (eduction my-identity (range 5)))

  10. Now if you run the above reduce as I suggest you'll see something very interesting. It prints 10. Even though your eduction earlier printed (0 0 1 1 2 2 3 3 4 4) (which if you add together is 20). What's going on here?

  11. As stated earlier, your transducer has a flaw. It doesn't work properly. The problem is that you call your rf and then call it a second time again in your arity 2 function. In clojure, stuff isn't mutable, unless it's somehow internally mutable for optimization purposes :). Here the problem is that sometimes clojure uses mutation and you get duplicates even though you never properly capture the result of your first time you call (rf) in your arity 2 function (as the argument to your println).

Let's fix you function but leave the second rf call in there:

  (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))))

Note:

  • I renamed xf to rf as noted earier.
  • Now we can see that you use the result of you rf and pass it on to the second call of rf. This transducer is not an identity transducer but doubles every element

Observe carefully:

(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

To aswer your questions:

  1. eduction doesn't really pass nil as the result argument when it's being reduced. It only gets nil when being printed which calls the Iterable interface.
  2. The nil really comes from TransformerIterator which is a special class created for transducers. This class is also used for sequence as you noticed. As the docs state:

The resulting sequence elements are incrementally computed. These sequences will consume input incrementally as needed and fully realize intermediate operations. This behavior differs from the equivalent operations on lazy sequences.

The reason that you receive nil as the result argument is because an iterator has no resulting collection which holds the elements iterated over so far. It simply goes over each element. No state is being accumulated.

You can see the reducing function that is used by the TransformerIterator as and inner class here:

https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/TransformerIterator.java

Do a CTRL+f and enter xf.invoke to see how your transducer is getting called.

The sequence function isn't really as lazy as a truly lazy sequence but I think this explains this part of you question:

Are Clojure Transducers Eager? (not lazy)

sequence simply computes the results of a transducer incrementally. Nothing else.

Lastly, a proper identity function with some debug statements:

(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))))

这篇关于Clojure换能器行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆