通读一遍(除了 clojure 源代码),有点难以理解转换器如何避免使用中间集合,这应该使它们更加精简和高效。
一个相关的问题是,他们假设每个输入变换是否独立于它的其他元素应用于其输入的每个元素,如果转换器通过压缩输入变换来工作,则可能存在限制输入集合 ? 逐个元素。
他们是否检查其输入函数的代码以确定如何交织它们以产生正确的组合结果?
在这些方面,您能否详细说明 clojure 中的换能器如何在引擎盖下工作?
一个相关的问题是,他们假设每个输入变换是否独立于它的其他元素应用于其输入的每个元素
它们被命名为转换器,因为它们可能具有(隐式)状态。
转换器是一个函数,它接受一个归约函数并返回一个归约函数。
归约函数是一个需要两个参数的函数:一个累加器和一个项目,并返回一个更新的累加器。
这是保存可变状态(如果有)的减少函数。
要获得换能器,您必须了解它们分两次工作:合成时间和计算时间。这就是为什么它们是返回函数的函数。
让我们从一个简单的还原功能启动:conj
。
返回的换能器(map inc)
是(fn [rf] (fn [acc x] (rf acc (inc x))))
。当用conj
它调用时返回一个等价于的函数(fn [acc x] (conj acc (inc x)))
。
返回的换能器(filter odd?)
是(fn [rf] (fn [acc x] (if (odd? x) (rf acc x) acc)))
。当用conj
它调用时返回一个等价于的函数(fn [acc x] (if (odd? x) (conj acc x) acc)))
。这个很有趣,因为rf
(下游减少功能有时会短路)。
如果你想链接这两个传感器,你只要(comp (map inc) (filter odd?))
传递conj
给这个复合传感器,(filter odd?)
将是第一个包装conj
(因为comp
从右到左应用函数)。然后将结果filtered-rf
函数传递给(map inc)
它,产生一个等价于的函数:
(fn [acc x] (filtered-rf acc (inc x)))
哪里filtered-rf
是(fn [acc x] (if (odd? x) (conj acc x) acc)))
。如果你内联,filtered-rf
你会得到:(fn [acc x] (let [x+1 (inc x)] (if (odd? x+1) (conj acc x+1) acc)))
.
如您所见,没有分配中间集合或序列。
对于有状态转换器来说,情况是一样的,只是减少函数具有可变状态(尽可能少并避免将所有以前的项目保留在其中):通常是一个 volatile 框(请参阅volatile!
参考资料)或一个可变的 Java 对象。
您可能还注意到,在示例中,项目首先被映射然后被过滤:计算是从左到右应用的,这似乎与comp
. 这不是:记住comp
这里由传感器组成,fns 包装减少 fns。因此,在合成时,包装从右到左发生(conj
由“过滤 rf”包装,然后由“映射 rf”包装),但在计算时,包装层向内遍历:map、filter 和 conj。
要实现您自己的转换器(reduced
, init 和 completion 参数),需要了解一些繁琐的实现细节,但总体思路是上面公开的。