Clojure 传感器行为 [英] Clojure transducers behavior

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

问题描述

在新的 clojure 1.7 中,我决定了解可以在哪里使用转换器.我了解它们可以带来什么好处,但我找不到编写带有解释的自定义转换器的正常示例.

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

(定义我的身份 [xf](fn([](println "Arity 0.")(xf))([结果](println "Arity 1: " result " = " (xf result))(xf 结果))([结果输入](println "Arity 2: " result input " = " (xf result input))(xf 结果输入))))

我从文档示例中获取了变量[result input] 的命名.我认为这就像在 reduce 函数中 result 是减少的部分而 input 是新的集合元素.

所以当我制作 (transduce my-identity + (range 5)) 时,我得到了我期望的结果 10.然后我读了edduction,但我不明白它是什么.反正我做了 (eduction my-identity (range 5)) 并得到:

Arity 2: nil 0 = nilArity 2:nil 1 = nil第 1 个参数:nil = nil(0 0 1 1)

每一项都被复制了,因为我在 println 语句中调用了 xf.为什么它将每个项目复制两次?为什么我没有?在进行教育时,我会一直得到零吗?我可以转述这种行为吗?

无论如何我都做到了

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

好的,结果是一个Eduction,它不是可还原的,而是像列表一样打印.为什么不能还原?当我输入 (doc eduction) 我明白了

返回传感器的可简化/可迭代应用程序到 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 参数而 eductionsequence ?

3) 我可以总是确定 nileductionsequence 吗?

4) 什么是 eduction 以及它不可还原的惯用想法是什么?或者如果是,那么我如何减少它?

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

6) 我可以用转换器创建实际的惰性序列吗?

解决方案

问题很多,先从几个回答开始:

  1. 是的,xf==xform 是一个转换器".

  2. 您的 my-identity 函数无法编译.你有一个参数,然后函数的多个其他参数.我相信你忘记了 (fn ...).

  3. 您对身份转换器的论点称为 xf.然而,这是通常称为 rf,意思是归约函数".现在令人困惑的部分是xf 也在减少函数(因此 comp 可以正常工作).然而,令人困惑的是,您将其称为 xf 而您应该将其称为 rf.

  4. 转换器通常是构建"的因为它们可能是有状态的和/或传递的参数.在您的情况下,您不需要构建它,因为它是简单,没有状态,甚至没有参数.但是请注意,您会通常将您的函数包装在另一个 fn 返回函数中.这意味着你会必须调用 (my-identity) 而不是将它作为 my-identity 传递.同样,这里很好,只是有点不合常规,可能会令人困惑.

  5. 让我们先继续假设您的 my-identity 转换器是正确(不是,我稍后会解释发生了什么).

  6. eduction 相对较少使用.它创建了一个过程".IE.你可以一遍又一遍地运行它,看看结果.基本上,只要就像您有包含您的项目的列表或向量一样,演绎将保持"应用换能器的结果.请注意,要实际做任何事情,您仍然需要一个rf(减少函数).

  7. 一开始我觉得考虑reduce函数是有帮助的作为 conj(或实际上 conj!)或在你的情况下 +.

  8. 你的 eduction 打印它产生的元素,因为它实现了 Iterable它由 println 或您的 REPL 调用.它只是打印出每个使用 arity 2 调用添加到传感器中的元素.

  9. 您对 (reduce + (eduction my-identity (range 5))) 的调用不起作用由于 Eduction(在 eduction 中构造的对象)只实现IReduceInit.IReduceInit 顾名思义,它需要一个首字母价值.所以这会起作用:(reduce + 0 (eduction my-identity (range 5)))

  10. 现在如果你运行上面的 reduce 我建议你会看到一些非常有趣的.它打印 10.即使你之前的 eduction 打印了 (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([](println "Arity 0.")(射频))([结果]{:post [(do (println "Arity 1 " %) true)]:pre [(do (println "Arity 1 " result) true)]}(射频结果))([结果输入]{:post [(do (println "Arity 2 " %) true)]:pre [(do (println "Arity 2 " result input) true)]}(rf (rf 结果输入) 输入))))

注意:

  • 如前所述,我将 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)(进入 '() my-identity (range 5));;=>(4 3 2 1 0)(进入 [] my-identity (range 5));;=>[0 0 1 1 2 2 3 3 4 4](进入 '() 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 时才会变为 nil界面.
  2. nil 真的来自 TransformerIterator 这是一个特殊的类为换能器创建.如您所见,此类也用于 sequence.正如文档所述:

<块引用>

结果序列元素是增量计算的.这些序列将根据需要逐步消耗输入并完全实现中间操作.这种行为不同于对惰性的等效操作序列.

您收到 nil 作为 result 参数的原因是因为迭代器没有保存到目前为止迭代过的元素的结果集合.它只是遍历每个元素.没有状态正在累积.

您可以在此处看到 TransformerIterator as 和内部类使用的归约函数:

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

执行 CTRL+f 并输入 xf.invoke 以查看您的传感器如何被调用.

sequence 函数并不像真正的惰性序列那样惰性,但我认为这解释了你问题的这一部分:

Clojure 转换器是否渴望?

sequence 只是增量地计算换能器的结果.没有什么否则.

最后,带有一些调试语句的正确标识函数:

(defn my-identity-prop [rf](fn([](println "Arity 0.")(射频))([结果](让 [r (rf 结果)](println "my-identity(" result ") =" r)r))([结果输入](让 [r (rf 结果输入)](println "my-identity(" 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?

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

Lastly, a proper identity function with some debug statements:

(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 传感器行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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