Каррирование функций на практике

16 октября 2026
Автор

Антон Ларичев

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

Каррирование (currying) — это техника функционального программирования, при которой функция с несколькими аргументами преобразуется в последовательность функций, каждая из которых принимает ровно один аргумент.

Название происходит от имени математика Хаскелла Карри, хотя саму идею впервые описал Моисей Шейнфинкель.

Простейший пример: обычная функция сложения выглядит так:

function add(a, b) {
  return a + b;
}

add(2, 3); // 5

Каррированная версия той же функции:

function curriedAdd(a) {
  return function(b) {
    return a + b;
  };
}

curriedAdd(2)(3); // 5

Функция curriedAdd принимает a и возвращает новую функцию, которая принимает b и возвращает результат. Вызов происходит через цепочку скобок.

Курс по теме

Основы JavaScript — курс

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Ручное каррирование

До того как перейти к автоматизации, важно понять ручной подход — он хорошо показывает механику.

function multiply(a) {
  return function(b) {
    return function(c) {
      return a * b * c;
    };
  };
}

multiply(2)(3)(4); // 24

// Можно разбить на шаги
const double = multiply(2);
const sixTimes = double(3);
sixTimes(4); // 24

Здесь виден ключевой эффект каррирования — возможность зафиксировать часть аргументов и получить специализированную функцию. double — это multiply(2), то есть функция, которая всегда умножает на 2.

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

const multiply = a => b => c => a * b * c;

const double = multiply(2);
double(5)(3); // 30

Универсальная функция curry

Ручное каррирование для каждой функции — утомительно. На практике пишут универсальную функцию curry, которая автоматически каррирует любую функцию.

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return function(...moreArgs) {
      return curried.apply(this, args.concat(moreArgs));
    };
  };
}

Как это работает:

  1. fn.length — это количество параметров исходной функции.
  2. Если переданных аргументов достаточно (args.length >= fn.length), вызываем исходную функцию.
  3. Если аргументов не хватает, возвращаем новую функцию, которая ждёт остальные аргументы и вызывает curried заново с объединёнными аргументами.

Пример использования:

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

Универсальная curry поддерживает смешанный вызов — аргументы можно передавать по одному или группами в любом сочетании.

Практические примеры

Фильтрация и трансформация массивов

Один из самых распространённых случаев применения — создание переиспользуемых предикатов и трансформаторов для работы с массивами.

const curry = fn => {
  const arity = fn.length;
  return function curried(...args) {
    if (args.length >= arity) return fn(...args);
    return (...moreArgs) => curried(...args, ...moreArgs);
  };
};

const filter = curry((predicate, array) => array.filter(predicate));
const map = curry((transform, array) => array.map(transform));
const reduce = curry((reducer, initial, array) => array.reduce(reducer, initial));

// Создаём специализированные функции
const filterEven = filter(x => x % 2 === 0);
const double = map(x => x * 2);
const sum = reduce((acc, x) => acc + x, 0);

const numbers = [1, 2, 3, 4, 5, 6, 7, 8];

filterEven(numbers);  // [2, 4, 6, 8]
double(numbers);      // [2, 4, 6, 8, 10, 12, 14, 16]
sum(numbers);         // 36

Валидация данных

Каррирование удобно для построения гибких валидаторов:

const validate = curry((rule, errorMessage, value) => {
  if (!rule(value)) {
    return { valid: false, error: errorMessage };
  }
  return { valid: true, error: null };
});

const isNotEmpty = validate(value => value.trim().length > 0);
const isEmail = validate(value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value));
const isMinLength = curry((min, value) => value.length >= min);

const validateRequired = isNotEmpty('Поле не может быть пустым');
const validateEmail = isEmail('Введите корректный email');
const validatePassword = validate(isMinLength(8), 'Минимум 8 символов');

console.log(validateRequired(''));            // { valid: false, error: 'Поле не может быть пустым' }
console.log(validateEmail('test@mail.ru'));   // { valid: true, error: null }
console.log(validatePassword('12345'));       // { valid: false, error: 'Минимум 8 символов' }

Логирование с контекстом

const log = curry((level, context, message) => {
  const timestamp = new Date().toISOString();
  console.log(`[${timestamp}] [${level}] [${context}]: ${message}`);
});

const logError = log('ERROR');
const logInfo = log('INFO');

const authLogger = logError('AUTH');
const dbLogger = logInfo('DATABASE');

authLogger('Неверный пароль для пользователя admin');
// [2026-10-01T12:00:00.000Z] [ERROR] [AUTH]: Неверный пароль для пользователя admin

dbLogger('Соединение установлено');
// [2026-10-01T12:00:00.000Z] [INFO] [DATABASE]: Соединение установлено

HTTP-запросы

const fetchData = curry(async (baseUrl, headers, endpoint) => {
  const response = await fetch(`${baseUrl}${endpoint}`, { headers });
  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }
  return response.json();
});

const apiRequest = fetchData('https://api.example.com');

const authenticatedRequest = apiRequest({
  'Authorization': 'Bearer token123',
  'Content-Type': 'application/json',
});

// Теперь достаточно передать только endpoint
authenticatedRequest('/users');    // GET https://api.example.com/users
authenticatedRequest('/products'); // GET https://api.example.com/products

Каррирование и частичное применение

Эти два понятия часто путают, но между ними есть принципиальное различие.

Каррирование — преобразование функции f(a, b, c) в f(a)(b)(c). Каждый вызов принимает ровно один аргумент.

Частичное применение — фиксация одного или нескольких аргументов функции для получения новой функции с меньшим числом параметров.

// Частичное применение через bind
function add(a, b, c) {
  return a + b + c;
}

const add5 = add.bind(null, 5);    // фиксируем a = 5
add5(3, 2);  // 10

const add5and3 = add.bind(null, 5, 3); // фиксируем a = 5, b = 3
add5and3(2); // 10

// Частичное применение вручную
function partial(fn, ...presetArgs) {
  return function(...laterArgs) {
    return fn(...presetArgs, ...laterArgs);
  };
}

const multiply = (a, b, c) => a * b * c;
const triple = partial(multiply, 3);
triple(2, 4); // 24

Универсальная curry из предыдущего раздела фактически совмещает оба подхода — она позволяет как передавать аргументы по одному, так и группами.

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

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

const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

const curry = fn => {
  const arity = fn.length;
  return function curried(...args) {
    if (args.length >= arity) return fn(...args);
    return (...moreArgs) => curried(...args, ...moreArgs);
  };
};

const prop = curry((key, obj) => obj[key]);
const gt = curry((threshold, value) => value > threshold);
const map = curry((fn, arr) => arr.map(fn));
const filter = curry((fn, arr) => arr.filter(fn));

const users = [
  { name: 'Алексей', age: 28 },
  { name: 'Мария', age: 17 },
  { name: 'Дмитрий', age: 34 },
  { name: 'Анна', age: 15 },
];

// Получить имена пользователей старше 18 лет
const getAdultNames = pipe(
  filter(user => gt(17, user.age)),
  map(prop('name'))
);

getAdultNames(users); // ['Алексей', 'Дмитрий']

Ограничения и подводные камни

Функции с переменным числом аргументов

Универсальная curry опирается на fn.length, а это свойство не учитывает rest-параметры и значения по умолчанию:

function broken(a, b = 0, ...rest) {
  return a + b;
}

console.log(broken.length); // 1 — b и rest не считаются!

const curriedBroken = curry(broken);
curriedBroken(1)(2); // не работает как ожидается

Для таких функций каррирование нужно делать вручную или явно указывать арность:

function curryN(arity, fn) {
  return function curried(...args) {
    if (args.length >= arity) return fn(...args);
    return (...moreArgs) => curried(...args, ...moreArgs);
  };
}

const sum = (...args) => args.reduce((a, b) => a + b, 0);
const curriedSum3 = curryN(3, sum);

curriedSum3(1)(2)(3); // 6

Читаемость кода

Цепочки вроде f(a)(b)(c)(d) могут быть сложны для восприятия в команде, где не все знакомы с функциональным программированием. Используйте каррирование там, где оно действительно упрощает код, а не ради самого паттерна.

Производительность

Каждый вызов в цепочке создаёт новое замыкание. В критических по производительности участках это может быть ощутимо. Для hot path предпочтительнее обычные функции.

Итог

Каррирование — это инструмент, который позволяет:

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

Начните с простых случаев — каррированные предикаты для filter и трансформаторы для map. Это самый естественный способ почувствовать пользу от паттерна в реальном коде.

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

Стрелочка влевоСтрелочные функции в JavaScript

Постройте личный план изучения Javascript до уровня Middle — бесплатно!

Javascript — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Javascript

Как работает метод toUpperCase() - JavaScriptКак работает метод trim() - JavaScriptКак работает метод toLowerCase() - JavaScriptКак работает метод substring() - JavaScriptКак работает метод startsWith() - JavaScriptКак работает метод split() - JavaScriptКак работает метод search() - JavaScriptКак работает метод slice() - JavaScriptКак работает метод replaceAll() - JavaScriptКак работает метод replace() - JavaScriptКак работает метод repeat() - JavaScriptКак работает метод padStart() - JavaScriptКак работает метод matchAll() - JavaScriptКак работает метод padEnd() - JavaScriptКак работает метод localeCompare() - JavaScriptКак работает метод match() - JavaScriptКак работает свойство length - JavaScriptКак работает метод lastIndexOf() - JavaScriptКак работает метод indexOf() - JavaScriptКак работает метод includes() - JavaScriptКак работает метод fromCodePoint() - JavaScriptКак работает метод fromCharCode() - JavaScriptКак работает метод endsWith() - JavaScriptКак работает метод concat() - JavaScriptКак работает метод codePointAt() - JavaScriptКак работает метод charCodeAt() - JavaScriptКак работает метод charAt() - JavaScript
Итератор в JavaScript
Событие wheel в JavaScriptСобытие unload в JavaScriptСобытие touch в JavaScriptСобытие submit в JavaScriptСобытие scroll в JavaScriptСобытие reset в JavaScriptМетод .preventDefault() в JavaScriptСобытие mouseover в JavaScriptСобытие mouseout в JavaScriptСобытие load в JavaScriptСобытие keyup в JavaScriptСобытие keydown в JavaScriptСобытие invalid в JavaScriptСобытие input в JavaScriptСобытийная модель Event в JavaScriptОбъект события Event в JavaScriptСобытие DOMContentLoaded в JavaScriptСобытие dblclick в JavaScriptСобытие click в JavaScriptСобытие change в JavaScriptBroadcast Channel API в JavaScriptJavaScript BroadcastChannel — межвкладочное взаимодействиеСобытие beforeunload в JavaScript
try...catch в JavaScriptError в JavaScript
Error cause — цепочки ошибок в JavaScript
Методы массивов toSorted, toReversed и withКак работает метод some() - JavaScriptКак работает метод reverse() - JavaScriptКак работает метод reduce() - JavaScriptКак работает метод map() - JavaScriptКак работает метод isArray() - JavaScriptКак работает метод indexOf() - JavaScriptКак работает метод includes() - JavaScriptКак работает метод from() - JavaScriptКак работает метод forEach() - JavaScriptКак работает метод flat() - JavaScriptКак работает метод findIndex() - JavaScriptКак работает метод find() - JavaScriptКак работает метод filter() - JavaScriptКак работает метод every() - JavaScriptArray.at, findLast, findLastIndex — новые методы массивовМассивы в JavaScriptКак работает метод flatMap() - JavaScript
Открыть базу знаний

Лучшие курсы по теме

изображение курса

Основы JavaScript

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий