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

欢迎!请参阅 关于 页面以获取更多有关此功能的信息。

+3
转换器
重新分类

我正在尝试实现一个简单的有状态转换,该转换器可以计算项目数量(使用 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]。

我还尝试使用 net.cgrand.xforms/count 从 xforms 库,并得到相同的不意外结果

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

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

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

我原本以为我在有状态的计数器实现中犯了实现错误导致了循环。但是我对 xforms/count 产生相同(不意外)的结果感到困惑。

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

有人能帮我理解为什么在有状态的转换器应用在 core.async 通道上会得到无限序列吗?

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

1 答案

+1
by

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

by
谢谢!添加 "(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?
by
嗯,手册(https://clojure.org/reference/transducers)说,“完成(阶数1)... 此阶数必须恰好调用rf完成阶数一次。” 它可以说得更广泛或语气更强——“永远只调用一次”。 无论如何,你的程序现在遵守该规则并可以工作。 顺便说一句,据我所知,可以通过GitHub上的网站项目提出文档增强的建议,项目地址为https://github.com/clojure/clojure-site
by
这个调用不会正好只调用一次rf完成数吗?

```
 (xf (xf result @counter))
```
by
编辑了 by
我的理解一直是,这个要求是传递性的:你应该只调用一次完成数,但你的自己完成数也必须只被调用一次。
对我来说,`partition-all`清空更关乎内存问题(不保留对可回收对象的过度引用),而非变换器。
(已编辑以更正自动更正)
...