← Назад к вопросам

Напишите функцию каррирования

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

АспектCurryPartial
ЦельЦепочка функций с одним аргументомФиксирует некоторые аргументы
ИспользованиеФункциональное программированиеПереиспользование логики
ГибкостьВысокая, любой порядокФиксирует слева направо

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 явно.

Напишите функцию каррирования | PrepBro