Каррирование в JS
Сегодня столкнулся с одной задачкой, которая по своему типу напоминает типичную вайтборд-задачу.
Есть функция:
const add = (a, b, c, d) => a + b + c + d;
Для неё нужно реализовать функцию-обёртку возвращающую соответствующие значения при таких вызовах:
const a = getWrap(add, 1)
console.log(a(2)(4)); // 8
console.log(a(1)(2)(3)); // 9
const b = getWrap(add);
console.log(b(5)(5, 5)); // 15
console.log(b(5, 5)(5)); // 15
Одним из условий является использование каррирования.
Карринг (currying) или каррирование — термин функционального программирования, который означает создание новой функции путём фиксирования аргументов существующей.
В этом случае функция getWrap является каррирующей функцией, принимающая в качестве аргументов каррируемую функцию и значение, на которое необходимо заменить первый аргумент каррируемой функции (заменять можно сколь угодно много аргументов, но в этом случае я заменяю только первый), причём нужно учесть, что аргумент может отсутствовать.
Промежуточное значение вычислений будет храниться в переменной count, а каррированная функция задаваться следующим образом:
const curried = val ? fn.bind(null, val) : fn;
Если требуется зафиксировать переменную, то каррируем переменную с помощью метода bind(), в который в качестве контекста вызова передаём null, так как в данном случае он нам не важен, а следующим аргументом — фиксируемый аргумент.
Если фиксировать переменную не требуется, то каррированная и каррируемая функции будут одинаковы.
Результатом выполнения каррированной функции будет функция, сохраняющая вычисления в переменную count и возвращающая саму себя, так же следует учесть количество аргументов, передаваемых в эту функцию, так как результатом вызова функции add с аргументами, хотя бы один из которых не задан (является undefined) будет NaN:
const func = function (...par) {
const params = [...par];
for (let i = 0; i < curried.length; i++) {
params[i] = params[i] || 0;
}
count += curried(...params);
return func;
};
Когда в функцию больше не передаётся параметров, нужно возвращать результат, это задачу решим с помощью метода объектов valueOf() (функция является объектом), а так же обнулять сохранённое значение результата, чтобы при следующем вызове вычисления были корректны:
func.valueOf = function () {
const result = count;
count = 0;
return result;
};
В целом моё решение задачи имеет следующий вид:
function getWrap(fn, val = 0) {
let count = 0;
const curried = val ? fn.bind(null, val) : fn;
const func = function (...par) {
const params = [...par];
for (let i = 0; i < curried.length; i++) {
params[i] = params[i] || 0;
}
count += curried(...params);
return func;
};
func.valueOf = function () {
const result = count;
count = 0;
return result;
};
return func;
}