Что такое каррирование в JavaScript?
Определение
Каррирование (currying) — техника трансформации функции с несколькими аргументами в последовательность функций, каждая из которых принимает ровно один аргумент и возвращает следующую функцию (либо итоговый результат, когда аргументы закончились). Название происходит от фамилии математика Хаскеля Карри.
Обычная функция:
function sum(a, b, c) {
return a + b + c;
}
sum(1, 2, 3); // 6
Каррированная версия:
function curriedSum(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
curriedSum(1)(2)(3); // 6
Зачем нужно каррирование
- Позволяет создавать специализированные функции из общих, фиксируя часть аргументов.
- Упрощает композицию функций в функциональном стиле (pipe/compose), так как каждая функция в цепочке принимает один аргумент.
- Повышает переиспользование логики: общая функция один раз описана, дальше она конфигурируется под конкретные задачи.
- Делает код декларативнее при построении обработчиков, валидаторов и фабрик функций.
Каррирование vs частичное применение
Это близкие, но разные вещи. Каррирование всегда превращает функцию от N аргументов в цепочку из N функций, каждая от одного аргумента. Частичное применение (partial application) — это фиксация произвольного количества аргументов заранее, при этом оставшаяся функция может принимать сразу несколько аргументов, не обязательно по одному.
// частичное применение через bind
function multiply(a, b, c) {
return a * b * c;
}
const double = multiply.bind(null, 2);
double(3, 4); // 24 — сразу два аргумента, это не каррирование
Универсальная функция curry
На собеседовании часто просят написать функцию curry, которая каррирует функцию произвольной арности:
function curry(fn) {
return function curried(...args) {
// если аргументов достаточно — вызываем исходную функцию
if (args.length >= fn.length) {
return fn.apply(this, args);
}
// иначе возвращаем функцию, которая ждёт остальные аргументы
return function (...rest) {
return curried.apply(this, args.concat(rest));
};
};
}
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
curriedSum(1, 2, 3); // 6
Важный нюанс: реализация опирается на fn.length, поэтому не работает корректно с функциями, использующими остаточные параметры или параметры по умолчанию — такую арность нужно указывать вручную.
Практический пример использования
const log = curry(function (level, tag, message) {
console.log(`[${level}] [${tag}] ${message}`);
});
const logError = log('ERROR');
const logAuthError = logError('AUTH');
logAuthError('Не удалось авторизовать пользователя');
// [ERROR] [AUTH] Не удалось авторизовать пользователя
Такой подход удобен для создания фабрик функций: обработчиков событий, валидаторов и преобразователей данных в pipeline-цепочках.
Что хочет услышать интервьюер
Кандидат даёт точное определение: превращение функции с N аргументами в цепочку функций от одного аргумента
Показывает разницу между каррированием и частичным применением
Может написать вручную каррированную функцию и универсальную curry-обёртку
Понимает, где каррирование полезно на практике: композиция, фабрики функций, переиспользование
Знает про ограничение с fn.length при работе с rest-параметрами
Пример: Ручное каррирование через вложенные функции
function curriedSum(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
curriedSum(1)(2)(3); // 6
Пример: Универсальная функция curry
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function (...rest) {
return curried.apply(this, args.concat(rest));
};
};
}
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 log = curry(function (level, tag, message) {
console.log(`[${level}] [${tag}] ${message}`);
});
const logError = log('ERROR');
const logAuthError = logError('AUTH');
logAuthError('Не удалось авторизовать пользователя');
// [ERROR] [AUTH] Не удалось авторизовать пользователя
Типичные ошибки
Путают каррирование с частичным применением, считая это одним и тем же
Не могут написать универсальную функцию curry без подглядывания
Забывают, что curry на основе fn.length ломается на функциях с ...rest или значениями по умолчанию
Считают, что каррирование обязательно про производительность, хотя цель — гибкость и композиция
Не приводят практического примера применения, ограничиваясь только теорией


