Шпаргалка по каррированию: что это, зачем нужно, примеры и практические случаи.
Каррирование в JavaScript
Каррирование — это преобразование функции от нескольких аргументов в цепочку функций, каждая из которых принимает один аргумент.
Названо в честь математика Хаскелла Карри. В функциональном программировании это базовый приём: он позволяет собирать новые функции из старых, не дублируя код.
Обычная функция vs каррированная
// обычная функция
function add(a, b, c) {
return a + b + c;
}
add(1, 2, 3); // 6
// каррированная версия
function addCurried(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
addCurried(1)(2)(3); // 6
Каждый вызов возвращает новую функцию, которая «помнит» предыдущий аргумент через замыкание.
Зачем это нужно
- Частичное применение — можно зафиксировать часть аргументов и получить новую функцию.
- Переиспользование — одна каррированная функция превращается в набор специализированных.
- Композиция — каррированные функции удобно соединять в цепочки.
- Читаемость — код становится декларативнее, когда логика собирается из маленьких кирпичиков.
Пример: частичное применение
const multiply = (a) => (b) => a * b;
const double = multiply(2);
const triple = multiply(3);
double(5); // 10
triple(5); // 15
Мы один раз описали умножение, а потом получили сколько угодно «специализированных» функций — без дублирования кода.
Пример: каррирование для работы с массивами
const map = (fn) => (arr) => arr.map(fn);
const filter = (fn) => (arr) => arr.filter(fn);
const reduce = (fn, init) => (arr) => arr.reduce(fn, init);
const doubleAll = map((x) => x * 2);
const keepEven = filter((x) => x % 2 === 0);
const sum = reduce((a, b) => a + b, 0);
const nums = [1, 2, 3, 4, 5];
doubleAll(nums); // [2, 4, 6, 8, 10]
keepEven(nums); // [2, 4]
sum(nums); // 15
// композиция
sum(keepEven(doubleAll(nums))); // 2 + 4 + 6 + 8 + 10 = 30
Такой стиль позволяет собирать обработку данных из готовых «полуфабрикатов», как из конструктора.
Универсальная функция 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));
};
};
}
const add3 = curry((a, b, c) => a + b + c);
add3(1)(2)(3); // 6
add3(1, 2)(3); // 6
add3(1)(2, 3); // 6
add3(1, 2, 3); // 6
Функция работает с любым количеством аргументов и позволяет вызывать каррированную версию как угодно: по одному, группами или сразу всеми.
Каррирование и композиция
Каррированные функции особенно хороши в композиции — когда результат одной функции передаётся в другую.
const compose = (...fns) => (x) =>
fns.reduceRight((acc, fn) => fn(acc), x);
const trim = (s) => s.trim();
const lower = (s) => s.toLowerCase();
const replace = (from) => (to) => (s) => s.split(from).join(to);
const slugify = compose(
replace(' ' )('-'),
lower,
trim
);
slugify(' Hello World '); // "hello-world"
Обрати внимание на replace: она каррирована, поэтому
её легко вставить в цепочку, зафиксировав одни аргументы и оставив
«свободным» входное значение.
Пример: работа с API и конфигом
const request = (baseUrl) => (headers) => (endpoint) => (options) =>
fetch(baseUrl + endpoint, {
...options,
headers: { ...headers, ...options?.headers },
});
// фиксируем базовый URL и заголовки один раз
const api = request('https://api.example.com')({
'Content-Type': 'application/json',
});
// теперь работаем только с endpoint'ами
const getUser = api('/user');
const getPosts = api('/posts');
const getPost = (id) => api(`/posts/${id}`)({ method: 'GET' });
getUser({ method: 'GET' });
getPost(42)({ method: 'GET' });
Это классический паттерн для HTTP-клиентов: конфигурация фиксируется один раз, а дальше код работает с короткими специализированными функциями.
Каррирование vs частичное применение
Эти термины часто путают, но разница есть.
- Каррирование — всегда превращает функцию от N аргументов в N функций по одному аргументу.
- Частичное применение — фиксирует любое количество аргументов и возвращает функцию, ждущую остальные.
// каррирование: строго по одному
const curried = (a) => (b) => (c) => a + b + c;
curried(1)(2)(3);
// частичное применение: можно фиксировать сколько угодно
function partial(fn, ...fixed) {
return (...rest) => fn(...fixed, ...rest);
}
const add = (a, b, c) => a + b + c;
const addOne = partial(add, 1);
addOne(2, 3); // 6
На практике их часто смешивают и называют «каррированием» оба приёма. Это нормально, но полезно понимать разницу.
Когда каррирование не нужно
- Простые функции без повторного использования — если функция вызывается один раз, каррировать её незачем.
- Горячий код — каждая обёртка создаёт замыкание, а это небольшие накладные расходы. В критичных местах иногда проще написать обычную функцию.
- Команда не знает FP — каррирование требует привычки. Если коллеги не в теме, код может показаться «магическим».
Что запомнить
-
Каррирование — это
f(a, b, c)→f(a)(b)(c). - Главная польза — частичное применение и композиция.
-
Пиши универсальную
curry()один раз и используй везде. - Особенно хорошо работает в пайплайнах обработки данных и в конфигурации API-клиентов.
- Не каррируй ради каррирования — только там, где это упрощает код.