我曾见过这个bug报告: https://clojure.atlassian.net/browse/CLJ-1569。
现在,在尝试为了教育目的而在另一种语言中实现类似Clojure的转换器时,我意识到这件事影响了很多东西。所以先来分享一下...
在一般情况下,使用当前实现进行懒初始化需要在步骤函数和完成函数中执行额外的检查,以防空输入。所以并不是没有解决方法,但这非常不直观,尤其是因为所有标准转换器仍然有单元函数版本,这些函数永远不会被调用(因此从实现的角度看,这是在过程中跳过了一步,但从逻辑上看,它增加了一个隐式的步骤,即删除转换后的初始值并替换为无关的值)。
在给出任何其他示例之前,我应该指出,linked问题中的alt-transduce没有检查初始化过程中的初始值是否已经被折叠(reduced),我认为它应该这样做,因为reduce自身不会这样做。所以我的更新的alt-reduce是
(defn alt-transduce
([xform f coll]
(let [rf (xform f)
result (rf)]
(rf (if (reduced? result)
(unreduced result)
(reduce rf (rf) coll)))))
([xform f init coll]
(let [rf (xform
(fn
([] init)
([result] (f result))
([result input] (f result input))))
result (rf)]
(rf (if (reduced? result)
(unreduced result)
(reduce rf (rf) coll))))))
谈到受影响的懒性和额外的检查,标准库中已经有了示例。take定义为
(defn take
...
([n]
(fn [rf]
(let [nv (volatile! n)]
(fn
([] (rf))
([result] (rf result))
([result input]
(let [n @nv
nn (vswap! nv dec)
result (if (pos? n)
(rf result input)
result)]
(if (not (pos? nn))
(ensure-reduced result)
result)))))))
...
首先,take 0仍然需要消费一个项,因为这它是第一次控制。其次,尽管我不知道为什么它以这种方式实现得有些复杂,但只有一个要跟踪的值,但有显然有两个比较,这可能只是因为它必须处理n = 0的情况,当它接收到额外的输入。在有有效初始化阶段的情况下,它可以像这样实现
(defn alt-take
([n]
(fn [rf]
(let [nv (volatile! n)]
(fn
([]
(let [result (rf)]
(if (not (pos? n))
(ensure-reduced result)
result)))
([result] (rf result))
([result input]
(let [nn (vswap! nv dec)
result (rf result input)]
(if (not (pos? nn))
(ensure-reduced result)
result))))))))
(非常接近原始实现,如果需要可以重新排序,重点是两个比较在应有的位置。)
如果这个问题还没有解决,那一定有某个强有力的原因?看起来这个问题从未得到回答,我想我的问题在这个时刻是相同的: https://clojure.atlassian.net/browse/CLJ-1569?focusedCommentId=18296。
我将非常感谢你能提供的任何关于为什么做出这种设计选择的见解。