Гид по технологиям

Каррирование в JavaScript: как и зачем использовать

6 min read JavaScript Обновлено 09 Jan 2026
Каррирование в JavaScript: руководство
Каррирование в JavaScript: руководство

Женщина с MacBook Pro за рабочим столом

Что такое каррирование

Каррирование (currying) названо в честь математика Хаскелла Б. Карри и восходит к лямбда-исчислению. Идея простая: взять функцию, которая ожидает несколько аргументов, и представить её как последовательность унарных (по одному параметру) функций. Каждая вложенная функция принимает один аргумент и возвращает либо значение, либо следующую функцию, пока не будут переданы все аргументы.

Определение в одну строку: каррирование — это преобразование f(a, b, c) → f(a)(b)(c).

Важно: каррированная функция — это не то же самое, что функция с частично применёнными аргументами (хотя эти понятия часто используются вместе). Частичное применение — это результат вызова каррированной функции с некоторыми фиксированными аргументами.

Простой пример каррирования

Ниже — базовый пример на обычных функциях:

function buildSandwich(ingredient1) {  
  return (ingredient2) => {  
    return (ingredient3) => {  
      return `${ingredient1},${ingredient2},${ingredient3}`  
    }  
  }  
}  

Функция buildSandwich возвращает вложенные анонимные функции. Каждая принимает один аргумент и возвращает либо следующую функцию, либо итоговую строку.

Если вызвать buildSandwich(“Bacon”), вы получите функцию (частично применённую):

console.log(buildSandwich("Bacon"))

Вывод консоли браузера, показывающий, что функция возвращает функцию.

Чтобы завершить вызов, передайте все аргументы:

buildSandwich("Bacon")("Lettuce")("Tomato")

Альтернативно, используя стрелочные функции, запись компактнее:

const buildMeal = ingred1 => ingred2 => ingred3 =>
  `${ingred1}, ${ingred2}. ${ingred3}`;

buildMeal("Bacon")("Lettuce")("Tomato");

Стрелочные функции упрощают синтаксис и делают цепочки более читабельными.

Частично применённые функции

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

Пример простого умножения:

const multiply = (x, y) => x * y;
const curriedMultiply = x => y => x * y;

const timesTen = curriedMultiply(10);
console.log(timesTen(8)); // 80

timesTen — частично применённая функция: она «захватила» x = 10 и ждёт лишь y.

Реальный пример для DOM-обновления:

const updateElemText = id => content =>
  document.querySelector(`#${id}`).textContent = content;

const updateHeaderText = updateElemText('header');
updateHeaderText("Hello World!");

Такой подход удобен, когда одно и то же действие (обновление элемента, логирование, запрос к API) применяется к разным данным, но с одинаковым «контекстом».

Композиция функций через каррирование

Каррирование хорошо сочетается с композицией: вы будто наслаиваете простые преобразования одно за другим. Например, в 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");

Скриншот с результатами композиции каррированных функций.

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

Универсальная функция curry — как превратить любую функцию в каррированную

Если вы используете каррирование часто, полезно иметь хелпер, который превращает обычную функцию в каррированную. Простейшая реализация:

const curry = (fn) => {
  return curried = (...args) => {
    if (fn.length !== args.length) {
      return curried.bind(null, ...args)
    }

    return fn(...args);
  }
}

Пояснение:

  • fn.length — количество формальных параметров функции fn (арность).
  • Если передано меньше аргументов, чем ожидает fn, возвращается связанная (bound) версия curried с накопленными аргументами.
  • Когда аргументов достаточно — вызывается исходная функция.

Пример:

const total = (x, y, z) => x + y + z;
const curriedTotal = curry(total);
console.log(curriedTotal(10)(20)(30)); // 60

Важно: реализация на основе fn.length не работает ожидаемо для функций с rest-параметрами или с неявной арностью (например, когда функция получает переменное число аргументов). Для таких случаев нужен другой подход.

Когда каррирование не подходит — контрпример

  • Функция с динамическим числом аргументов (rest/variadic) или с опциональными параметрами: fn.length не отражает реальную логику, поэтому простая curry-реализация ошибочна.
  • Простые одноразовые утилиты: каррирование может усложнить код, если оно не приносит явных преимуществ.
  • Производительность: глубокие цепочки функций создают более длинный call stack и дополнительные замыкания; в критичных по производительности местах это важно учитывать.

Пример, где каррирование бессмысленно:

// Функция, которая по сути просто возвращает массив всех аргументов
const gather = (...items) => items;
// Каррирование тут не даёт пользы: вызвать её каррированно неудобно

Альтернативы и готовые инструменты

  • Function.prototype.bind — можно частично фиксировать параметры через bind: const timesTen = multiply.bind(null, 10).
  • Библиотеки: lodash/fp и ramda предоставляют удобные curry/partial/composition-хелперы (_.curry, R.curry).
  • Частичное применение (partial) vs каррирование: partial фиксирует аргументы не обязательно слева, каррирование зависит от порядка и унарности.

Практическое руководство: когда и как вводить каррирование в кодовую базу

Короткая методология:

  1. Определите повторяющиеся операции с повторяющимся контекстом (например, обновление пяти разных элементов с одинаковой логикой).
  2. Реализуйте чистую функцию с явной арностью.
  3. Добавьте curry-хелпер или используйте библиотеку.
  4. Напишите тесты для частично применённых функций и для сценариев композиции.
  5. При ревью кода оцените читаемость: не заменяет ли каррирование простые и очевидные вызовы.

Критерии приёмки

  • Функция корректно возвращает частично применённую функцию при передаче части аргументов.
  • Поведение соответствует для всех допустимых комбинаций аргументов.
  • Нет регрессий по производительности в горячих путях (профайлинг при необходимости).

Роль‑ориентированные чеклисты

  • Для разработчика:

    • Есть ли повторяющаяся логика, которую можно вынести в частично применённую функцию?
    • Написаны ли тесты для частично применённых вариантов?
    • Документированы ли ожидания по арности функции?
  • Для ревьюера:

    • Понятно ли имя возвращаемой функции и её назначение?
    • Не ухудшила ли каррированная версия локальную читабельность?
    • Нельзя ли заменить на более простую реализацию через bind или вспомогательную функцию?

Тесты и критерии приёмки

Примеры тест-кейсов (юнит‑тесты):

  • Вызов curriedTotal(1)(2)(3) возвращает 6.
  • Частично применённая функция timesTen(5) возвращает 50.
  • При вызове с недостаточным числом аргументов возвращается функция, готовая принять оставшиеся аргументы.
  • Функция с rest-параметрами не должна использоваться с простым curry-алгоритмом (ожидается выброс ошибки или документированное поведение).

Советы по отладке и поддержке

  • Пишите короткие имена и документацию: f => g => h легко читать, пока имена отражают смысл.
  • Используйте логирование внутри декораторов (addCustomer/processOrder) осторожно — это может загрязнить консоль.
  • Для сложных цепочек можно давать промежуточные имена функциям, чтобы стек вызовов в ошибках оставался информативным.

Важно: каррирование — это инструмент. Оно улучшает читабельность и повторное использование, но не является обязательной парадигмой для каждого проекта.

Совместимость и миграция

  • Каррирование — это уровень абстракции в коде: оно не зависит от версии JavaScript, если используется синтаксис, поддерживаемый целевой средой (стрелочные функции, bind и т.д.).
  • Для старых сред (ES5) варианты с Function.prototype.bind работают лучше.
  • При миграции из процедурного стиля постепенно выносите повторяемую логику в чистые функции, затем применяйте curry там, где это оправдано.

Ментальные модели и эвристики

  • Думайте о каррировании как о «фиксировании контекста» — вы создаёте специализированную вариацию общей функции.
  • Если вы можете правильно назвать частично применённую функцию (например, createUserWithDefaults), это хороший индикатор того, что каррирование улучшит код.
  • Если название частичной функции становится бессмысленным или слишком длинным —, возможно, каррирование усложняет код.

Мини‑шпаргалка (cheat sheet)

  • curry(fn): превращает функцию типа (a,b,c) в a => b => c => result.
  • partial(fn, a): фиксирует первый аргумент через bind.
  • compose(f, g)(x) = f(g(x)) — полезно вместе с каррированными функциями.

Пример compose с каррированными функциями:

const compose = (f, g) => (...args) => f(g(...args));

const double = x => x * 2;
const square = x => x * x;
const doubleThenSquare = compose(square, double);
console.log(doubleThenSquare(3)); // (3*2)^2 = 36

Диаграмма принятия решения

flowchart TD
  A[Нужно ли переиспользование контекста?] -->|Да| B{Функция имеет фиксированную арность?}
  A -->|Нет| C[Не использовать каррирование]
  B -->|Да| D[Использовать каррирование или partial]
  B -->|Нет| E[Рассмотреть альтернативы: bind, объект настроек]
  D --> F[Добавить тесты для частично применённых функций]
  E --> F

Краткий глоссарий

  • Арность: количество формальных параметров функции.
  • Частичное применение: создание функции с некоторыми фиксированными аргументами.
  • Замыкание: функция помнит окружение, где была создана.

Итог и рекомендации

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

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

Не применяйте каррирование механически: для простых или динамических функций оно может добавить лишнюю сложность.

Ключевые шаги внедрения:

  1. Выделите чистые функции.
  2. Решите, какая арность имеет значение.
  3. Используйте curry-хелпер или библиотеку.
  4. Напишите тесты и добавьте документацию.

Спасибо за чтение — начните с небольшого примера в своём проекте и оцените преимущества для читаемости и повторного использования.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство