Как реализовать более общую функцию сокращения, чтобы позволить ранний выход?
reduce
(акаfoldL
в FP) - самая общая итерационная функция высшего порядка в Javascript. Вы можете реализовать, например,map
или жеfilter
с точки зренияreduce
, Я использовал императивный цикл, чтобы лучше проиллюстрировать алгоритм:
const foldL = f => acc => xs => {
for (let i = 0; i < xs.length; i++) {
acc = f(acc)(xs[i]);
}
return acc;
};
const map = f => xs => {
return foldL(acc => x => acc.concat([f(x)]))([])(xs);
}
let xs = [1, 2, 3, 4];
const inc = x => ++x;
map(inc)(xs); // [2, 3, 4, 5]
Но вы не можете получитьsome
или жеevery
отreduce
потому что оба могут вернуться рано.
Так как же может выглядеть еще более обобщенная функция частичного сокращения? До сих пор я придумал следующую наивную реализацию:
const foldLP = f => pred => acc => xs => {
for (let i = 0, r; i < xs.length; i++) {
r = pred(i, acc, xs[i]);
if (r === true) { // normal iteration
acc = f(acc)(xs[i]);
} else if (r === false) { // early exit
break;
} /* else { // skip iteration
continue;
} */
}
return acc;
};
const takeN = n => (idx, acc, x) => idx < n;
const append = xs => ys => xs.concat(ys);
let xs = [1,2,3,4,5];
foldLP(append)(takeN(3))([])(xs); // [1,2,3]
Я также могу реализоватьmap
с точки зренияfoldLP
:
const always = x => y => x;
const map = f => xs => {
return foldLP(acc => x => acc.concat([f(x)]))(always(true))([])(xs);
}
map(inc)(xs); // [2,3,4,5,6]
Недостаток очевиден: всякий раз, когда механизм раннего выхода не нужен,always
вызывается без необходимости. Функция преобразования и раннего выхода статически состоит изfoldLP
и не может использоваться независимо. Существует ли более эффективный комбинатор, позволяющий обобщатьArray.prototype.reduce
?
Если вы посмотрите на стек вызовов, оператор возврата сокращающей функцииacc => x => acc.concat([f(x)])
пришлось бы пропустить несколько стековых кадров. Этот вид стековых манипуляций заставляет меня думать о продолжениях. Может быть, есть эффективный способ решить эту проблему в Continuation Passing Style вместе с адаптированной функцией call / cc - или, по крайней мере, с генератором.