事实证明,一旦你习惯了 CPS,reduce 的泛化就可以很容易地实现:
const foldL = f => acc => xs => xs.length
? f(acc)(xs[0])(xss => foldL(f)(xss)(xs.slice(1)))
: acc;
const map = f => foldL(acc => x => cont => cont(acc.concat([f(x)])))([]);
const filter = pred => foldL(acc => x => cont => cont(pred(x) ? acc.concat([x]) : acc))([]);
const every = pred => foldL(acc => x => cont => pred(x) ? cont(true) : false)(true);
const some = pred => foldL(acc => x => cont => pred(x) ? true : cont(false))(false);
const takeN = n => foldL(acc => x => cont => acc.length < n ? cont(acc.concat([x])) : acc)([]);
const comp = f => g => x => f(g(x));
const not = f => x => !f(x);
const inc = x => ++x;
const odd = x => x & 1;
const even = not(odd);
const lt3 = x => x < 3;
const eq3 = x => x === 3;
const sqr = x => x * x;
const xs = [1, 2, 3, 4, 5];
map(inc)(xs); // [2, 3, 4, 5, 6]
filter(even)(xs); // [2, 4]
every(lt3)(xs); // false
some(lt3)(xs); // true
takeN(3)(xs); // [1, 2, 3]
// we can compose transforming functions as usual
map(comp(inc)(sqr))(xs); // [2, 5, 10, 17, 26]
// and the reducing functions as well
comp(map(inc))(filter(even))(xs); // [3, 5]
comp(takeN(2))(filter(odd))(xs); // [1, 3]
正如您所看到的,这并不是真正纯粹的 CPS,而是与 Direct Style 的混合。这样做有一个很大的好处foldL
通常的转换函数必须不携带任何额外的延续参数,但保持其正常的签名。
我仅在部分代码中使用 CPS 函数,它们在实现所需行为方面是不可替代的。 CPS 是一个极其强大的构造,您总是会尽可能使用最不表达的构造。
comp(takeN(2))(filter(odd))(xs)
说明了实施的弱点之一(可能还有其他弱点)。归约函数的组合不会发生在数组元素的级别。因此需要一个中间数组([1, 3, 5]
)在计算最终结果之前([1, 3]
)。但这是传感器的问题......