Каррирование

Currying — превращение функции от n аргументов в цепочку n функций, каждая от одного аргумента. По формуле автора: «каррирование — это рекурсивное частичное применение».

Что это / Зачем

  • Из f(a, b, c) делаем f(a)(b)(c)
  • Можно закрепить часть аргументов и передать остальные позже
  • Универсальная абстракция: подойдёт для математики, файлов, рендеринга, фильтрации сигналов
  • Связано с замыканиями и Partial Application

Варианты вызова каррированной функции

const f = curry(sum4);
f(1, 2, 3, 4);   // все сразу
f(1, 2, 3)(4);   // частично + остаток
f(1)(2)(3)(4);   // по одному
f(1, 2)(3, 4);   // комбинации

Чистая реализация

const curry = (fn) => (...args) =>
  args.length >= fn.length
    ? fn(...args)
    : curry(fn.bind(null, ...args));
  • fn.length — арность функции (число объявленных аргументов)
  • Если аргументов достаточно — вызываем fn
  • Иначе — bind закрепляет часть, оборачиваем в curry

Ключевые правила

  • fn.length не учитывает rest-параметры → REST ломает каррирование
  • Каждые `` — отдельный вызов, отдельный шаг рекурсии
  • Возвращается всегда новая функция, пока аргументов мало
  • Каррирование = рекурсивный partial

Подводные камни

  • Дефолтные значения уменьшают length → могут сломать арность
  • function(a, b, ...rest)length === 2, rest игнорируется
  • Плохая реализация (с глубокой вложенностью IIFE) — нечитаема и не отлаживается
  • Стрелочные функции каррируются как и обычные, но без своего this

🎓 Источник: Частичное применение и каррирование в JavaScript

  • 📅 2018-10-08 · YouTube · ID: ND8KQ5xjk7o
  • Тезисы:
    • «Каррирование — это рекурсивное частичное применение»
    • Чистая реализация в 3 строки vs плохая curryBad с IIFE — урок о читаемости
    • Универсальная абстракция: подходит для любых функций
    • fn.length определяет арность, REST её ломает
    • Оптимизированный curry пропускает шаг рекурсии при первом вызове
  • Цитата: «Запутанно написанный код вы даже никак не отдебажите. Короче, кто сможет исправить вот в этом коде ошибку, считайте, сдали экзамен.»
  • Код:
    // Оптимизированный curry с частичным применением сразу
    const curry = (fn, ...args1) =>
      args1.length >= fn.length
        ? fn(...args1)
        : (...args2) => curry(fn, ...args1, ...args2);
    
    const sum4 = (a, b, c, d) => a + b + c + d;
    const f = curry(sum4);
    f(1)(2)(3)(4); // 10
    f(1, 2)(3, 4); // 10
    

🎓 Источник: Вложенные функции в JavaScript, V8 оптимизация

  • 📅 2025-02-16 · YouTube · ID: O62ZgsmB80M
  • Тезисы:
    • Паттерн (a) => (b) => ... (две стрелочные) — специально оптимизируется V8
    • V8 кэширует шаблон возвращаемой функции, не пересоздаёт её каждый раз
    • На 3-4 функциях оптимизация становится сложнее, но всё ещё работает
    • Замыкание как контекст — альтернатива классу, V8 это «понимает»
    • Не бояться таких конструкций — кажущийся оверхед не реален
  • Цитата: «Именно этот паттерн, именно вот такая последовательность из двух функций, особым образом оптимизируют, предполагая, что первая функция специально вызывается ради того, чтобы в замыкании сохранить какие-то аргументы.»

См. также