Каррирование функций в JavaScript: понятие, примеры и практическое применение

Каррирование — это функциональная техника, которая делает JavaScript-код более выразительным и модульным. В статье объясняется, что такое каррирование, как получать частично применённые функции, как использовать каррирование для композиции функций, а также варианты применения и ограничения.
Что такое каррирование
Каррирование названо в честь математика Хаскела Б. Карри и берёт корни в лямбда-исчислении. Суть: функцию с несколькими параметрами преобразуют в последовательность унарных функций — каждая принимает по одному аргументу и возвращает следующую функцию, пока не будут переданы все параметры.
Определение в одну строку: каррированная функция принимает один аргумент и возвращает либо результат (если параметры закончились), либо следующую функцию.
Важно: каррирование — это не то же самое, что частичное применение, но их часто используют вместе. Частичное применение фиксирует часть аргументов, оставляя остальное для последующих вызовов.
Базовый пример каррирования
Ниже — пример каррированной функции (JavaScript). Код сохранён в оригинальном виде для точности:
function buildSandwich(ingredient1) {
return (ingredient2) => {
return (ingredient3) => {
return `${ingredient1},${ingredient2},${ingredient3}`
}
}
}Функция buildSandwich возвращает вложенные анонимные функции: сначала принимает ingredient1, затем ingredient2, затем ingredient3 и, наконец, возвращает строку-результат. Когда вы вызываете buildSandwich(“Bacon”), вы получаете функцию, которая ждёт следующий аргумент.
console.log(buildSandwich("Bacon"))Чтобы завершить цепочку и получить итог, вызовите все функции подряд:
buildSandwich("Bacon")("Lettuce")("Tomato")Такая запись показывает, что buildSandwich фактически распадается на три унарные функции. Для более компактного синтаксиса часто используют стрелочные функции:
const buildMeal = ingred1 => ingred2 => ingred3 =>
`${ingred1}, ${ingred2}. ${ingred3}`;
buildMeal("Bacon")("Lettuce")("Tomato")Стрелочные функции делают код короче и чище, особенно при глубокой вложенности.
Частично применённые функции
Частичное применение — практическая цель каррирования. Вы фиксируете часть аргументов и получаете новую функцию с «заблокированными» значениями.
Простой нелинейный пример (обычная функция):
const multiply = (x, y) => x * y;Каррированная версия:
const curriedMultiply = x => y => x * y;Фиксируем множитель 10:
const timesTen = curriedMultiply(10);
console.log(timesTen(8)) // 80Это удобно, когда одна и та же операция применяется с разными данными, но с одним общим параметром.
Ещё пример ближе к DOM и веб-разработке:
const updateElemText = id = content
=> document.querySelector(`#${id}`).textContent = content
// Lock the element's id into the function:
const updateHeaderText = updateElemText('header')
// Update the header text
updateHeaderText("Hello World!")Тут updateElemText фиксирует id, возвращая функцию, которая обновляет текст для конкретного элемента.
Композиция функций с каррированием
Каррирование отлично сочетается с композицией: вы комбинируете небольшие функции в сложные пайплайны, при этом каждая отвечает за отдельный этап.
Пример из e‑commerce: три обёртки-функции, которые логируют и передают выполнение дальше:
const addCustomer = fn => (...args) => {
console.log("Saving customer info")
return fn(...args)
}
const processOrder = fn => (...args) => {
console.log(`processing order #${args[0]}`)
return fn(...args);
}
let completeOrder = (...args) => {
console.log(`Order #${[...args].toString()} completed.`);
}Порядок вызовов важен: обёртки нужно применять снаружи внутрь так, чтобы первая в логике оказалась самой внутренней при вызове:
completeOrder = (processOrder(completeOrder));
completeOrder = (addCustomer(completeOrder));
completeOrder("1000")Альтернативный «обычный» вариант показан в исходном тексте — выглядит громоздко из‑за вложенных function-выражений.
Автоматическое преобразование в каррированные функции
Если вы используете каррирование часто, удобно иметь утилиту, которая превращает обычную функцию в каррированную. Один из распространённых подходов — рекурсивный curry:
const curry = (fn) => {
return curried = (...args) => {
if (fn.length !== args.length) {
return curried.bind(null, ...args)
}
return fn(...args);
}
}Пример применения:
const total = (x, y, z) => x + y + z
const curriedTotal = curry(total)
console.log(curriedTotal(10)(20)(30)) // 60Замечание: реализация выше ориентирована на фиксированное число параметров fn.length; для функций с опциональными/остаточными параметрами нужно другую стратегию.
Когда каррирование не подходит
Important: каррирование — мощный инструмент, но не универсальный. Обычные сценарии, где каррирование нецелесообразно:
- Простые одноразовые вызовы, где нет повторного использования частично применённых версий. Каррирование добавит лишнюю сложность.
- Функции с динамическим количеством аргументов (rest-параметры) — стандартная curry-реализация на fn.length не работает корректно.
- Когда важна читаемость для команды, незнакомой с функциональным стилем. Иногда явное перечисление аргументов легче воспринимается.
- Производительность: каррирование создаёт дополнительные замыкания; в «горячих» участках кода это может быть ощутимо.
Альтернативы и дополнения
- Частичное применение (partial application) как отдельная техника — фиксирует несколько аргументов без полной трансформации в унарные функции.
- Паттерн фабрик функций: вместо каррирования вернуть функцию-конфигуратор, принимающий объект конфигурации.
- Использовать библиотеку (lodash/fp, Ramda), чтобы получить проверенные curry/compose/pipe утилиты.
- Для работы с несколькими аргументами и опциями удобнее применять объекты с именованными полями (options) — это решает проблемы порядка аргументов.
Шпаргалка: быстрые приёмы и шаблоны
- Создание частично применённой функции: const f2 = curry(f)(a)
- Комбинация логирующей обёртки: const withLog = fn => (…a) => { console.log(a); return fn(…a)}
- Составление пайплайна: const pipeline = (…fns) => arg => fns.reduce((v, fn) => fn(v), arg)
Пример pipeline:
const pipeline = (...fns) => x => fns.reduce((v, f) => f(v), x)
const trim = s => s.trim()
const toLower = s => s.toLowerCase()
const exclaim = s => s + '!'
const shout = pipeline(trim, toLower, exclaim)
console.log(shout(' Hello ')) // "hello!"Критерии приёмки
- Функция корректно возвращает частично применённую версию при передаче меньшего числа аргументов.
- Поведение для функций с rest-параметром документировано и ожидаемо.
- Нет утечек памяти при частом создании частично применённых функций (проверить в профайлере).
- Логирование/побочные эффекты не теряются при обёртках и композиции.
Модель принятия решения: использовать каррирование или нет
flowchart TD
A[Нужно ли фиксировать часть аргументов?] -->|Да| B[Есть ли фиксированное число аргументов?]
A -->|Нет| C[Не использовать каррирование]
B -->|Да| D[Использовать каррирование или curry-утилиту]
B -->|Нет| E[Использовать объекты-конфигурации или partial с именованными параметрами]
D --> F[Проверить производительность]
F --> G[Ок внедрить]Роли и чеклист внедрения
Разработчик:
- Выяснить, где повторно используется набор аргументов.
- Написать unit-тесты для частично применённых версий.
- Использовать существующую curry-утилиту (локальную или из библиотеки).
Руководитель команды:
- Оценить эффект на читабельность и обучаемость команды.
- Решить стандарты использования (когда применять каррирование).
QA:
- Проверить сценарии с разным числом аргументов.
- Протестировать побочные эффекты и взаимодействие с DOM (если есть).
Краткая справка (глоссарий)
- Каррирование: разбиение функции на цепочку унарных функций.
- Частичное применение: фиксирование части аргументов для получения новой функции.
- Композиция: объединение нескольких функций в один пайплайн.
Заключение
Каррирование — удобный инструмент функционального программирования в JavaScript. Оно помогает строить переиспользуемые, легко конфигурируемые функции и упрощает композицию. Но важно учитывать читаемость, характеристики производительности и требования к входным аргументам. Выберите каррирование там, где оно даёт чистую практическую выгоду: конфигурация, создание API‑обёрток, пайплайны обработки данных.
Похожие материалы
Трекер чтения и виртуальная полка в Notion
Сберегательный счёт Apple для держателей Apple Card
Карточки в Google Таблицах: Flippity шаг за шагом
Избегать платных дорог и шоссе в Google Maps
Массовые выплаты PayPal: экономьте на комиссиях