Currying
Curry реализуют на всех уровнях: джун — простая обёртка, сеньор — вариативный curry с поддержкой f(a)(b,c) и f(a,b)(c). Интервьюер проверяет знание Function.length, замыканий и рекурсии. Правильный ответ — накопление аргументов через fn.length с конкатенацией.
Вариативный curry через fn.length — поддерживает любое разбиение аргументов:
/** * Каррирование произвольной функции. * Поддерживает f(a)(b,c), f(a,b)(c), f(a,b,c), f(a)(b)(c). * @template T * @param {Function} fn * @returns {Function} */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)); }; };}
// usageconst add = curry((a, b, c) => a + b + c);
console.log(add(1)(2)(3)); // 6 — f(a)(b)(c)console.log(add(1, 2)(3)); // 6 — f(a,b)(c)console.log(add(1)(2, 3)); // 6 — f(a)(b,c)console.log(add(1, 2, 3)); // 6 — f(a,b,c)
// Частичное применениеconst add5 = add(5); // ждёт ещё 2 аргументаconsole.log(add5(3)(10)); // 18
// Point-free стильconst double = curry((factor, x) => factor * x)(2);const result = [1, 2, 3].map(double); // [2, 4, 6]
// Реальный пример: настраиваемый fetchconst request = curry((method, url, data) => fetch(url, { method, body: JSON.stringify(data) }));const post = request('POST');const getUser = post('/api/users');getUser({ name: 'Alice' }); // POST /api/usersРасширения: curryN для rest-функций и placeholder-версия:
// fn.length не учитывает: rest-параметры, параметры со значениями по умолчанию// function f(a, b = 1, ...rest) {} → f.length === 1 (только до первого default!)// Для таких функций — curryN с явной арностью:
function curryN(fn, arity) { return function curried(...args) { if (args.length >= arity) return fn.apply(this, args); return (...more) => curried(...args, ...more); };}
const sum = curryN((...xs) => xs.reduce((a, b) => a + b, 0), 3);console.log(sum(1)(2)(3)); // 6
// ─── Placeholder-версия (продвинутый уровень) ─────────────────────────────const _ = Symbol('placeholder');
function curryP(fn) { const arity = fn.length; return function curried(...args) { const filled = args.filter(a => a !== _).length; if (filled >= arity && !args.includes(_)) { return fn(...args.slice(0, arity)); } return function (...newArgs) { // Заменяем placeholder-ы на новые аргументы слева направо const merged = args.map(a => a === _ && newArgs.length ? newArgs.shift() : a ); return curried(...merged, ...newArgs); }; };}
// usage с placeholderconst divide = curryP((a, b) => a / b);const half = divide(_, 2); // _ — первый аргумент ожидается позжеconsole.log(half(10)); // 5const tenDivBy = divide(10, _);console.log(tenDivBy(2)); // 5Сложность
- Время: O(n) конкатенация при каждом частичном вызове, где n = накопленные args
- Память: O(n) — все накопленные аргументы живут в замыкании до финального вызова
Итог: Curry накапливает аргументы через замыкание до fn.length, затем вызывает оригинал. curryN — для rest/default-параметров. Placeholder-версия — для пропуска позиций.