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通道上运行此操作时,我得到了一系列无限次的5s

(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》)说,“完成(1个参数)... 这个参数必须正好调用一次rf完成参数。” 这可以更多地推广或强调——"无论何时都必须正好调用一次"。 无论如何,你的程序现在符合这个规则,并且可以工作。 P.S. 根据我所知,可以通过网站项目的GitHub问题来提出一个文档增强的建议,见《https://github.com/clojure/clojure-site》)
这不是调用 rf 完成算子的正好一次吗?

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

编辑
我总是认为这个要求是传递性的:你应该调用完成算子一次,但你的自己的完成算子也必须只调用一次。
对我来说,`partition-all` 清理更多的是关于内存问题(不保留对可回收对象的过度引用),而不是关于转换器。
(已编辑以纠正自动更正)
...