Что такое карри? Когда его использовать?


Каррирование (currying) — это техника преобразования функции, которая принимает несколько аргументов, в последовательность функций, каждая из которых принимает один аргумент. Вместо того чтобы передавать все аргументы сразу, как в обычной функции, каррированная функция принимает первый аргумент и возвращает новую функцию, которая принимает следующий аргумент, и так далее, пока не будут получены все аргументы. После этого происходит вызов исходной функции с полным набором аргументов.

Каррирование тесно связано с функциональным программированием и позволяет создавать более гибкий и переиспользуемый код.

Основная идея

Обычная функция:

function multiply(a, b) {
return a \* b;
}
multiply(2, 3); // 6

Каррированная версия:

function multiply(a) {
return function(b) {
return a \* b;
}
}
multiply(2)(3); // 6

Функция multiply(2) возвращает новую функцию, которая ожидает второй аргумент b.

Почему называется “currying”?

Название происходит от имени американского логика Хаскелла Карри (Haskell Curry), который популяризировал эту концепцию в контексте теоретической информатики и лямбда-исчисления. Однако сама идея была впервые предложена Мозесом Шёнфинкелем.

Различие между каррированием и частичным применением

Каррирование всегда преобразует функцию в цепочку унарных функций (по одному аргументу), в то время как частичное применение позволяет фиксировать только некоторые аргументы, но не обязательно один за другим.

Пример частичного применения:

function add(a, b, c) {
return a + b + c;
}
function partialAdd(a) {
return function(b, c) {
return add(a, b, c);
}
}
partialAdd(1)(2, 3); // 6

А каррирование:

function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
}
}
}
curriedAdd(1)(2)(3); // 6

Универсальная функция для каррирования

Реализация функции curry в JavaScript:

function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
}
};
}

Пример использования:

function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
curriedSum(1)(2)(3); // 6
curriedSum(1, 2)(3); // 6
curriedSum(1)(2, 3); // 6

Преимущества каррирования

Повторное использование кода
Можно создавать частично применённые функции и использовать их в разных контекстах.

const add5 = curriedAdd(5);

add5(10)(15); // эквивалентно add(5, 10, 15)
  1. Композиция функций
    Каррированные функции легко комбинируются с другими функциями, особенно в библиотеках вроде lodash/fp и ramda.

Упрощение API
При проектировании API каррирование позволяет строить функции с более понятной логикой:

<br/>const isEqualTo = a => b => a === b;
const isFive = isEqualTo(5);
isFive(5); // true
  1. Мемоизация и ленивые вычисления
    Частично применённые или каррированные функции позволяют отложить часть вычислений до момента, когда будут доступны все данные.

Где используется каррирование

  1. Функциональное программирование
    В функциональных языках (например, Haskell) все функции по умолчанию каррированы. В JavaScript каррирование используется в библиотеках типа Ramda, Lodash (fp-модули), Redux (для middleware и enhancer функций), React (в mapStateToProps и connect).

Обработка событий или конфигурации
Часто удобно сначала задать конфигурацию, а потом "внедрить" конкретные данные:

<br/>const greet = greeting => name => \`${greeting}, ${name}\`;
const sayHello = greet('Hello');
sayHello('John'); // Hello, John

Промежуточные вычисления в цепочках вызовов
Каррирование позволяет записывать компактный и читаемый код с промежуточными шагами:

<br/>const multiply = a => b => a \* b;
const double = multiply(2);
double(10); // 20

Redux и React
При использовании Redux, функции высшего порядка, такие как connect, используют каррирование:

connect(mapStateToProps)(Component)

Недостатки и ограничения

  1. Сложность для начинающих
    Концепция может быть непривычной и трудной для понимания новичками.

  2. Перегрузка абстракцией
    Избыточное использование каррирования может ухудшить читаемость кода.

  3. Производительность
    Из-за создания большого количества вложенных функций может увеличиваться нагрузка на сборщик мусора и стек вызовов.

  4. Не всегда удобно
    В некоторых случаях проще использовать обычные функции или частичное применение, особенно если требуется передать несколько аргументов сразу.

Каррирование — мощный инструмент в арсенале разработчика, особенно в контексте чистых функций, переиспользования и композиции. Хотя в JavaScript оно не встроено по умолчанию, с помощью простых функций или сторонних библиотек можно добиться каррированного поведения, улучшая модульность и читаемость кода.