请在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]。

我还尝试使用来自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))))))

我曾假设我在状态计数器实现中犯了错误,导致循环。但我对xforms/count产生相同(不预期的)结果感到困惑。

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

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

编辑:在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

这不是正好调用一次rf完成arity吗?

```
 (xf (xf result @counter))
```

编辑
我总是认为这个需求是有传递性的:你应该调一次完成阶数,但你自己的完成阶数也必须只调一次。
对我来说,“partition-all”清除与现实存取问题(不保留对可回收对象的过度引用)更为相关,而不是与转换器。
(已编辑以正确自动校正)
...