Подписаться на блог

Каррирование в 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;
}