← Назад к вопросам
Напишите функцию каррирования
3.0 Senior🔥 131 комментариев
#Node.js и JavaScript#Алгоритмы и структуры данных
Условие
Реализуйте функцию curry, которая преобразует обычную функцию в каррированную:
function curry(fn) {
// Ваш код
}
// Пример использования:
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6
console.log(curriedSum(1, 2, 3)); // 6
Что проверяется
- Понимание каррирования
- Работа с Function.length
- Рекурсия и замыкания
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Базовая реализация
function curry<T extends (...args: any[]) => any>(
fn: T
): (...args: any[]) => any {
const arity = fn.length; // Количество параметров функции
return function curried(...args: any[]): any {
// Если собрали достаточно аргументов, вызываем функцию
if (args.length >= arity) {
return fn(...args);
}
// Иначе возвращаем функцию, которая соберёт оставшиеся аргументы
return (...nextArgs: any[]) => curried(...args, ...nextArgs);
};
}
Как это работает
Принцип каррирования:
- Преобразует функцию с несколькими параметрами в цепочку функций с одним параметром
- Каждый вызов собирает аргумент и возвращает новую функцию
- Когда собрано достаточно аргументов, выполняется исходная функция
Пошаговый процесс для curriedSum(1)(2)(3):
Шаг 1: curriedSum(1)
- args = [1]
- arity = 3 (функция sum ожидает 3 параметра)
- 1 < 3, возвращаем новую функцию
Шаг 2: (...)(2)
- args = [1, 2]
- 2 < 3, возвращаем новую функцию
Шаг 3: (...)(3)
- args = [1, 2, 3]
- 3 >= 3, вызываем sum(1, 2, 3) → 6
Пример использования
function sum(a: number, b: number, c: number): number {
return a + b + c;
}
const curriedSum = curry(sum);
// Все варианты работают:
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6
console.log(curriedSum(1, 2, 3)); // 6
// Частичное применение:
const add1 = curriedSum(1);
const add1And2 = add1(2);
const result = add1And2(3); // 6
Расширенная версия с остаточными аргументами
function curry<T extends (...args: any[]) => any>(
fn: T,
arity: number = fn.length
): (...args: any[]) => any {
return function curried(...args: any[]): any {
if (args.length >= arity) {
return fn(...args.slice(0, arity));
}
return (...nextArgs: any[]) => curried(...args, ...nextArgs);
};
}
// Позволяет передавать больше аргументов чем нужно:
const curriedSum = curry(sum);
console.log(curriedSum(1, 2, 3, 4, 5)); // 6 (игнорирует 4 и 5)
Версия с поддержкой дополнительных параметров
function curry<T extends (...args: any[]) => any>(
fn: T,
expectedArity?: number
): any {
const arity = expectedArity ?? fn.length;
const curried = (...args: any[]): any => {
if (args.length >= arity) {
return fn(...args);
}
return (...nextArgs: any[]) => curried(...args, ...nextArgs);
};
// Добавляем метод для явного вызова с накопленными аргументами
curried.flush = () => fn();
return curried;
}
Практические примеры
Пример 1: Преобразование URL параметров
const formatURL = (protocol: string, domain: string, path: string) => {
return `${protocol}://${domain}${path}`;
};
const curriedFormat = curry(formatURL);
const httpFormatter = curriedFormat('https');
const siteFormatter = httpFormatter('example.com');
const url1 = siteFormatter('/api/users'); // https://example.com/api/users
const url2 = siteFormatter('/api/posts'); // https://example.com/api/posts
Пример 2: Логирование с контекстом
const log = (level: string, timestamp: string, message: string) => {
console.log(`[${timestamp}] ${level}: ${message}`);
};
const curriedLog = curry(log);
const infoLog = curriedLog('INFO')(new Date().toISOString());
infoLog('Приложение запущено');
infoLog('Запрос получен');
infoLog('Ответ отправлен');
Пример 3: Функции обработки данных
const multiply = (a: number, b: number, c: number) => a * b * c;
const curriedMultiply = curry(multiply);
const double = curriedMultiply(2);
const doubleThenTriple = double(3);
const result1 = doubleThenTriple(4); // 2 * 3 * 4 = 24
const result2 = doubleThenTriple(5); // 2 * 3 * 5 = 30
Тестирование
import { describe, it, expect } from 'vitest';
describe('curry', () => {
it('должен каррировать функцию', () => {
const sum = (a: number, b: number, c: number) => a + b + c;
const curriedSum = curry(sum);
expect(curriedSum(1)(2)(3)).toBe(6);
});
it('должен поддерживать частичное применение', () => {
const sum = (a: number, b: number, c: number) => a + b + c;
const curriedSum = curry(sum);
const addOne = curriedSum(1);
const addThree = addOne(2);
expect(addThree(3)).toBe(6);
});
it('должен поддерживать множественные аргументы за раз', () => {
const sum = (a: number, b: number, c: number) => a + b + c;
const curriedSum = curry(sum);
expect(curriedSum(1, 2)(3)).toBe(6);
expect(curriedSum(1)(2, 3)).toBe(6);
expect(curriedSum(1, 2, 3)).toBe(6);
});
it('должен работать с функциями разной арности', () => {
const add = (a: number, b: number) => a + b;
const curriedAdd = curry(add);
expect(curriedAdd(1)(2)).toBe(3);
const power = (a: number, b: number, c: number) => a ** b ** c;
const curriedPower = curry(power);
expect(curriedPower(2)(3)(2)).toBe(512); // 2^(3^2) = 2^9
});
});
Каррирование vs Partial Application
| Аспект | Curry | Partial |
|---|---|---|
| Цель | Цепочка функций с одним аргументом | Фиксирует некоторые аргументы |
| Использование | Функциональное программирование | Переиспользование логики |
| Гибкость | Высокая, любой порядок | Фиксирует слева направо |
Partial Application
function partial<T extends (...args: any[]) => any>(
fn: T,
...partialArgs: any[]
): (...args: any[]) => ReturnType<T> {
return (...args: any[]) => fn(...partialArgs, ...args);
}
// Использование:
const addOne = partial(sum, 1);
const result = addOne(2, 3); // sum(1, 2, 3) = 6
Function.length
Важно: Function.length возвращает количество параметров БЕЗ значений по умолчанию:
const sum = (a: number, b: number, c: number = 0) => a + b + c;
console.log(sum.length); // 2 (не 3!)
const multiply = (...args: number[]) => args.reduce((a, b) => a * b);
console.log(multiply.length); // 0 (rest параметры не считаются)
Именно поэтому иногда нужно передавать arity явно.