Что это и зачем
Определение
Каррирование превращает функцию f(a, b, c) в f(a)(b)(c). Не вызывает функцию — трансформирует её. Из одной функции с N аргументами получается N вложенных функций, каждая принимает ровно один аргумент.
// Обычная функция
function add(x, y) {
return x + y
}
add(2, 3) // 5
// Каррированная версия
function curriedAdd(x) {
return function (y) {
return x + y
}
}
curriedAdd(2)(3) // 5
Стрелочная запись делает каррирование визуально компактным:
const add = x => y => x + y
add(2)(3) // 5
Имя и происхождение
Термин назван в честь Хаскелла Карри — математика, исследовавшего комбинаторную логику. Но саму идею впервые описал Моисей Шёнфинкель в 1924 году. Карри развил её позже, и название закрепилось за ним.
Язык программирования Haskell тоже назван в честь Карри. В Haskell все функции каррированы по умолчанию.
Связь с замыканиями
Каждый вызов в цепочке создаёт замыкание, которое захватывает переданный аргумент:
const add = x => y => x + y
const add5 = add(5)
// add5 — это замыкание: (y) => 5 + y
// Переменная x = 5 захвачена из внешней области видимости
add5(3) // 8
add5(10) // 15
add(5) возвращает новую функцию, замкнутую на x = 5. Эта функция живёт, пока на неё есть ссылка, — точно как описано в главе про замыкания.
Зачем каррировать
Создание специализированных функций
Из одной общей функции порождаем семейство конкретных:
// Общая функция логирования
const log = level => message => `[${level}] ${message}`
// Специализированные логгеры
const warn = log('WARN')
const error = log('ERROR')
const info = log('INFO')
warn('Диск почти заполнен') // "[WARN] Диск почти заполнен"
error('Соединение потеряно') // "[ERROR] Соединение потеряно"
info('Сервер запущен') // "[INFO] Сервер запущен"
Не нужно каждый раз передавать 'WARN' — он зафиксирован при создании warn.
Переиспользование конфигурации
// Каррированный запрос к API
const request = baseUrl => endpoint => params =>
fetch(`${baseUrl}${endpoint}?${new URLSearchParams(params)}`)
// Фиксируем базовый URL
const api = request('https://api.example.com')
// Фиксируем эндпоинт
const getUsers = api('/users')
const getPosts = api('/posts')
// Вызываем с конкретными параметрами
getUsers({ page: 1, limit: 10 })
getPosts({ category: 'js' })
Подготовка функций для compose/pipe
Каррированные функции принимают по одному аргументу — их легко выстраивать в цепочки. Подробнее — в разделе compose и pipe.
Каррирование — не вызов
Частая ошибка: путать каррирование с немедленным вызовом. Каррирование ничего не вычисляет — оно создаёт новую структуру из функций.
const multiply = x => y => x * y
// Каррирование: создаём специализированную функцию
const double = multiply(2) // (y) => 2 * y — пока ничего не вычислено
// Вызов: вычисляем результат
double(10) // 20
Каррирование пришло из лямбда-исчисления Алонзо Чёрча (1930-е). В лямбда-исчислении все функции принимают ровно один аргумент — это фундаментальное ограничение. Функция от двух аргументов λxy.x+y — это синтаксический сахар для λx.(λy.x+y), то есть каррированная форма. JavaScript не имеет такого ограничения, поэтому каррирование — осознанный выбор, а не необходимость.
Итого
| Факт | Описание |
|---|---|
| Что это | Трансформация f(a, b, c) в f(a)(b)(c) |
| Что делает | Не вызывает функцию, а создаёт цепочку из вложенных функций |
| Механизм | Каждый вызов создаёт замыкание, захватывающее аргумент |
| Зачем | Специализация, переиспользование конфигурации, подготовка к compose/pipe |
| Происхождение | Лямбда-исчисление Чёрча, назван в честь Хаскелла Карри |