2024 Clojure 状况调查! 中分享您的想法。

欢迎!请查看 关于 页面以了解更多此平台如何运作的信息。

+3
Transducers
重新分类

我正在尝试实现一个简单的带状态的 transducer,用来计数元素数量(使用 ClojureScript)

(defn stateful-counter []
  (fn [xf]
    (let [counter (atom 0)]

      (fn
        ([] (xf))

        ([result]
         (xf (xf result @counter)))

        ([result _]
          (swap! counter inc)
          result)))))

当我在一个序列上运行这个程序时,我得到了输出

(into [] (stateful-counter) (range 5))

[5]

这正好是我的预期。

当我在一个 core.async 通道上运行这个程序时,我得到了一个由 5 组成的无限序列

(go (println
     (<! (let [c (async/chan 1 (stateful-counter))]

           (async/onto-chan! c (range 5))

           (async/into []
                       (async/take 10 c))))))

[5 5 5 5 5 5 5 5 5 5]

如果我不使用 (async/take 10 _),似乎会陷入无限循环。预期的结果是 [5]。

我还尝试使用 xforms 库中的 net.cgrand.xforms/count,得到了相同的不预期结果

(go (println
     (<! (let [c (async/chan 1 xforms/count)]

           (async/onto-chan! c (range 5))

           (async/into []
                       (async/take 10 c))))))

我曾假设我在 stateful-counter 的实现中犯了一个错误造成循环。但不理解 xforms/count 产生了同样的(不预期)结果。

这是一个 ClojureScript 片段,但我已经能够用 Clojure 产生相同的不预期结果。

能有人帮我理解为什么在 core.async 通道上应用带状态的 transducer 会导致无限序列的产生吗?

编辑:我也在 Clojure 上看到了相同的不预期行为。

1 答案

+1

看看 partition-by
https://github.com/clojure/clojure/blob/clojure-1.10.1/src/clj/clojure/core.clj#L7160
尤其是注释 ";;clear first!",它是 Clojure 核心中带有感叹号的两个注释之一。

谢谢!添加 "(reset! counter nil)"(类似于 partition-by 中的 "(.clear a)")就能让它工作

    (defn stateful-counter []
      (fn [xf]
        (let [counter (atom 0)]
    
          (fn
            ([] (xf))
    
               ([result]
             (let [c @counter]
               (reset! counter nil)
               (xf (xf result c))))
    
            ([result _]
             (swap! counter inc)
            result)))))

我还是不明白这是为什么。你能给我一些提示吗?

这也似乎是 xforms 库中的一个 bug?
嗯,手册(《https://clojure.org/reference/transducers》)说,“完成(arity 1)... 这个 arity 必须调用 rf 完成arity 恰好一次。” 它可以更加广泛或加强 - “正是这样”。 无论如何,你的程序现在符合这个规则,也可以工作了。 P.S. 根据我所知,可以通过 GitHub issue 在网站项目上提出增强文档的建议,链接为《https://github.com/clojure/clojure-site
by
这个调用不会只调用一次rf的完成arity吗?

```
 (xf (xf result @counter))
```
by
编辑了 by
我的理解始终是,这个要求具有传递性:你应该调用一次完成arity,但你的完成arity也应该只被调用一次。
对我来说,`partition-all`清空更多是关于内存问题(不是保留对可回收对象的过度引用),而不是关于transducer。
(已编辑以纠正自动修正)
...