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

欢迎!有关该功能的更多信息,请参阅关于 页面。

+3
in 转换器 by
recategorized by

我正在尝试实现一个简单的状态转换器,用于计算元素数量(使用 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]

这正是我所期望的。

当我在核心异步通道上运行它时,我得到了一系列无穷的 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))))))

我曾假设我在状态计数器(stateful-counter)的实现中犯了一个错误,导致了循环。但是,xforms/count 产生了相同(不期望的)结果。

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

有人能帮助我理解为什么在核心异步通道上应用状态转换器导致无限序列吗?

编辑:同样不期望的行为在 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 状态计数器 []
      (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)说,“完成(arity 1)... 这个arity必须正好调用一次rf完成arity。”可以更笼统或强调——”恰好一次,永远如此。”无论如何,你的程序现在遵守这个规则,并且可以工作。P.S. 根据我所知,可以通过GitHub issue在网站项目上提出一个文档改进建议,https://github.com/clojure/clojure-site
by
这不会正好调用rf完成arity一次吗?

```
 (xf (xf result @counter))
```
by
编辑了 by
我一直认为这个要求是传递性的:你应该调用一次完成性阶数,但你的完成性阶数也必须只调用一次。
对我来说,“partition-all”清除更多的是关于内存问题(不是保留对可回收对象的过度引用)而不是关于转换器。
(编辑以更正自动更正)
...