我之前见过这个错误报告: https://clojure.atlassian.net/browse/CLJ-1569。
现在,当我试图为了教育目的在另一种语言中实现类似于 Clojure 的变换器时,我意识到这影响了多少事情。所以首先是一些思考...
在一般情况下,使用当前实现懒惰地进行初始化需要在步进函数以及空输入的情况下的完成函数中执行额外的检查。所以并非没有解决方案,但这非常不直观,尤其是所有标准变换器仍有空 参数版本,这些版本永远不会被调用(所以从实现的角度来看,它在过程中跳过了一步,但从逻辑的角度来看,它在隐式地增加一个步骤,即丢弃转换后的初始值并用一个不相关值替换它)。
在提供任何例子之前,我应该指出,在所链接的问题中,alt-transduce 并未检查初始化过程后初始值是否已经归约,我认为这是应该做的,因为减少(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。
如果您能提供更多有关为何做出这一设计选择的见解,我将不胜感激。