转载

JavaScript 玩转 Clojure 大法之 Transducer

Reducer

说道reduce这个词, 想必JS Developer大多会用过underscore 2 (或类似)的reduce方法, 大概形式是这样

_.reduce(fn, 0, [1,2,3])

大概意思是初始为0, 应用fn到每一个collection(检测coll)元素上,得到一个新的值.

如果加上map, 比如(我要开始用 mori 3 了)

reduce(sum, 0, map(inc [1,2,3]))

Terminology:

  1. reducing 函数: 用来reduce的函数, 比如sum
  2. transform: 变换, 从一个函数变另一个函数
  3. xf: xform, transform 函数
  4. reducible: 可被reduce的,也就是实现reduce接口的,比如所有的collection

让我们一步一步分析一下这次reduce到底干了什么

  1. map 函数 inc 到 coll 每一个元素, 得到一个新的 coll [2,3,4]
  2. reduce 把新coll的每个元素用sum函数, 得到一个新的值.

好吧这就是reduce了, 用一个reducing函数sum去计算coll得出一个新的值.

来看看更好的解法

transform

reduce函数需要等待map返回新的coll后才能reduce, 那么可不可以一步直接算出来呢?

假如我们有一个函数xf可以变换reducing函数(上例的sum是reducing函数)的形式, 比如

xf(reduceFn) -> anotherReduceFn

再假如我们的新map函数可以做这种转换

map(inc)(sum) -> aShinyNewReduceFn

map 函数的简单transform实现可以这样实现,如果你感兴趣的话

function map(fn){   return function(reduceFn){     return function(result, input){       reduceFn(result, fn(input))     }   } }

那么我们之前的reduce就可以写成

reduce(map(inc)(sum),0,[1,2,3])

yeah, 现在只需要一步就reduce出来结果了, reduce应用 map(inc)(sum) 来计算值, 只需要遍历一遍coll

Reducer

但是如果我们不想改变map函数的接口, 原始形式的接口还是比较好写好读的

reduce(sum, 0, map(inc [1,2,3]))

那么需要进一步的抽象, 我把新的map函数叫做rmap好了

function rmap(fn, coll){   reducer(coll, map(fn)) }

跟以前接口一样,接收函数和coll,但是返回一个由reducer生成的reducible, 所以就变成了

reduce(sum, 0, reducer([1,2,3], map(inc)))

等等,怎么做到的…你已经消费了coll了, 那reducing函数怎么进来的, reducer怎么知道用sum去reduce呢.

Reducible

答案是, 反转reduce的关系, 原来reduce用sum去计算结果, 现在,我们调用reducible的reduce方法来计算结果

JavaScript 玩转 Clojure 大法之 Transducer

如果你还没有被我弄晕的话, 准备好, 又来一个新单词 reducible . 也就是可以被reduce的东西.

于是我们需要coll实现reduce方法,这样就成为reducible了.

也就是reduce函数现在应该长这样, 我们暂且叫它 rreduce

function rreduce(reduceFn, init, reducible){   reducible(reduceFn, init) }

那么我们的例子就变成了这样

reducer([1,2,3], map(inc))(sum, 0)

reducer接收coll和xf, 返回reducible函数. 这一切都是lazy的, 直到rreduce调用第coll行才执行.

function reducer(coll, xf){   return function(reduceFn, init){     return coll.reduce(xf(reduceFn), init) (coll)   } }
正文到此结束
Loading...