Curry: примеры (JAVASCRIPT)
curry(fn (function), ...args (any)): functionОсновные сведения о каррировании
Каррирование (currying) представляет собой функциональную технику, которая преобразует функцию с несколькими аргументами в последовательность функций, каждая из которых принимает один аргумент. Эта концепция названа в честь математика Хаскелла Карри.
Каррирование используется для создания специализированных функций на основе более общих, улучшения переиспользования кода и поддержки point-free стиля программирования. Метод часто применяется в функциональном программировании, обработке событий и конфигурации компонентов.
Функция каррирования в JavaScript не является встроенной в стандартную библиотеку, но реализуется пользователем или используется из библиотек (например, lodash). Классическая реализация принимает функцию и возвращает новую функцию, которая может принимать аргументы по одному или группами. Если переданное количество аргументов меньше ожидаемого, возвращается частично примененная функция, иначе происходит вызов исходной функции.
Аргументы реализации каррирования:
- func - исходная функция для каррирования
- arity (опционально) - количество аргументов, которые должна принять функция перед выполнением (по умолчанию используется длина функции func.length)
Возвращаемое значение - каррированная версия исходной функции, которая может принимать аргументы постепенно и возвращает либо результат выполнения (если переданы все аргументы), либо новую каррированную функцию (если аргументов недостаточно).
Простые примеры использования
Базовое каррирование с двумя аргументами:
function add(a, b) {
return a + b;
}
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
const curriedAdd = curry(add);
console.log(curriedAdd(2)(3));5
Каррирование с тремя аргументами:
const multiply = curry((a, b, c) => a * b * c);
const multiplyByTwo = multiply(2);
const multiplyByTwoAndThree = multiplyByTwo(3);
console.log(multiplyByTwoAndThree(4));24
Использование с различным количеством аргументов:
const sum = curry((a, b, c, d) => a + b + c + d);
console.log(sum(1, 2)(3, 4));
console.log(sum(1)(2, 3, 4));
console.log(sum(1)(2)(3)(4));10 10 10
Похожие функции в JavaScript
Частичное применение (partial application) - функция bind() создает новую функцию с предустановленными аргументами. В отличие от каррирования, где аргументы передаются строго по одному, частичное применение позволяет фиксировать несколько аргументов одновременно.
Функциональная композиция - комбинирование нескольких функций в одну цепочку вызовов. Используется для создания сложных преобразований из простых функций, тогда как каррирование фокусируется на преобразовании аргументов.
Замыкания (closures) - механизм, который часто используется при реализации каррирования для сохранения состояния аргументов между вызовами.
Функции высшего порядка - принимают или возвращают другие функции, что является основой для реализации каррирования.
Каррирование предпочтительнее использовать, когда нужно создавать специализированные функции из общих в функциональном стиле. Частичное применение лучше подходит для фиксации конкретных аргументов без строгого разделения на унарные функции.
Аналоги в других языках программирования
Python - functools.partial для частичного применения, каррирование реализуется через лямбда-функции:
from functools import partial
def add(a, b, c):
return a + b + c
curried_add = lambda a: lambda b: lambda c: add(a, b, c)
result = curried_add(1)(2)(3)
print(result)6
PHP - замыкания позволяют эмулировать каррирование:
$add = function($a) {
return function($b) use ($a) {
return function($c) use ($a, $b) {
return $a + $b + $c;
};
};
};
$result = $add(1)(2)(3);
echo $result;6
Haskell - каррирование встроено в язык, все функции по умолчанию каррированы:
add a b c = a + b + c
addOne = add 1
addOneTwo = addOne 2
result = addOneTwo 3
-- Результат: 66
C# - можно использовать лямбда-выражения:
Func>> curriedAdd = a => b => c => a + b + c;
int result = curriedAdd(1)(2)(3);
Console.WriteLine(result); 6
Распространенные ошибки
Использование функций с переменным числом аргументов без указания arity:
function sum(...args) {
return args.reduce((acc, val) => acc + val, 0);
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3));Функция выполнится после первого вызова, так как fn.length = 0
Потеря контекста this при каррировании методов объекта:
const calculator = {
factor: 10,
multiply(a, b) {
return a * b * this.factor;
}
};
const curriedMultiply = curry(calculator.multiply);
console.log(curriedMultiply(2)(3));Ошибка: this.factor равно undefined
Каррирование функции, использующей arguments:
function legacy(a, b) {
console.log(arguments.length);
return a + b;
}
const curriedLegacy = curry(legacy);
curriedLegacy(1);arguments работает некорректно при частичном применении
Изменения в последних версиях
В стандарте JavaScript ES6 появились стрелочные функции и rest-параметры, которые упрощают реализацию каррирования. Параметр функции length ведет себя по-разному для функций с значениями по умолчанию и rest-параметрами:
// ES6: стрелочные функции
const curry = (fn) => {
return function curried(...args) {
return args.length >= fn.length
? fn(...args)
: (...args2) => curried(...args, ...args2);
};
};
// Разное поведение length
const f1 = (a, b = 1) => a + b;
const f2 = (a, ...rest) => a + rest.length;
console.log(f1.length, f2.length);1 1
В ES2020 не было внесено специфических изменений для каррирования, но предложение о pipeline operator может улучшить читаемость каррированных функций в будущем.
Расширенные примеры применения
Каррирование в обработчиках событий:
const handleEvent = curry((eventType, element, handler) => {
element.addEventListener(eventType, handler);
});
const handleClick = handleEvent('click');
const handleClickOnButton = handleClick(document.getElementById('btn'));
handleClickOnButton((e) => console.log('Клик!'));Обработчик click события на кнопке
Создание конфигурируемых функций валидации:
const validate = curry((min, max, value) => {
return value >= min && value <= max;
});
const validateAge = validate(18, 120);
const validateTemperature = validate(-50, 50);
console.log(validateAge(25));
console.log(validateTemperature(100));true false
Композиция каррированных функций:
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);
const add = curry((a, b) => a + b);
const multiply = curry((a, b) => a * b);
const subtract = curry((a, b) => a - b);
const transform = compose(
add(10),
multiply(2),
subtract(5)
);
console.log(transform(20));55 // ((20 - 5) * 2) + 10
Ленивые вычисления с каррированием:
const lazyCurry = (fn) => {
const curried = (...args) => {
if (args.length >= fn.length) {
return fn(...args);
}
return Object.assign((...args2) => curried(...args, ...args2), {
valueOf: () => fn(...args)
});
};
return curried;
};
const calc = lazyCurry((a, b, c) => a * b + c);
const partial = calc(2, 3);
console.log(Number(partial)); // 6 + undefined = NaN
console.log(Number(partial(4))); // 2*3+4 = 10NaN 10
Каррирование с placeholder для пропуска аргументов:
const _ = Symbol('placeholder');
const curryWithPlaceholder = (fn) => {
return function curried(...args) {
const complete = args.length >= fn.length && !args.includes(_);
if (complete) return fn.apply(this, args);
return function(...args2) {
const newArgs = args.map(arg => arg === _ && args2.length ? args2.shift() : arg);
return curried(...newArgs, ...args2);
};
};
};
const pow = curryWithPlaceholder((a, b) => Math.pow(a, b));
const square = pow(_, 2);
const cube = pow(_, 3);
console.log(square(5)); // 25
console.log(cube(3)); // 2725 27