Что такое карри? Когда его использовать?
Каррирование (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)
- Композиция функций
Каррированные функции легко комбинируются с другими функциями, особенно в библиотеках вроде lodash/fp и ramda.
Упрощение API
При проектировании API каррирование позволяет строить функции с более понятной логикой:
<br/>const isEqualTo = a => b => a === b;
const isFive = isEqualTo(5);
isFive(5); // true
- Мемоизация и ленивые вычисления
Частично применённые или каррированные функции позволяют отложить часть вычислений до момента, когда будут доступны все данные.
Где используется каррирование
- Функциональное программирование
В функциональных языках (например, 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)
Недостатки и ограничения
-
Сложность для начинающих
Концепция может быть непривычной и трудной для понимания новичками. -
Перегрузка абстракцией
Избыточное использование каррирования может ухудшить читаемость кода. -
Производительность
Из-за создания большого количества вложенных функций может увеличиваться нагрузка на сборщик мусора и стек вызовов. -
Не всегда удобно
В некоторых случаях проще использовать обычные функции или частичное применение, особенно если требуется передать несколько аргументов сразу.
Каррирование — мощный инструмент в арсенале разработчика, особенно в контексте чистых функций, переиспользования и композиции. Хотя в JavaScript оно не встроено по умолчанию, с помощью простых функций или сторонних библиотек можно добиться каррированного поведения, улучшая модульность и читаемость кода.