Перейти к содержимому

Каррирование и частичное применение в JavaScript

Куррирование и частичное применение — это мощные техники функционального программирования, которые могут значительно повысить модульность и повторное использование вашего кода JavaScript. В этой статье мы подробно разберём эти концепции, рассмотрим лучшие практики и приведём практические примеры, которые помогут вам освоить их применение.

Понимание куррирования

Куррирование — это техника, при которой функция преобразуется в последовательность функций, каждая из которых принимает один аргумент. Это позволяет разбить функцию, принимающую несколько аргументов, на ряд унарных (одноаргументных) функций.

Пример куррирования

Рассмотрим простую функцию, которая складывает три числа:


javascript
function add(a, b, c) {
    return a + b + c;
}

Мы можем преобразовать эту функцию в каррированную версию:


Output appears here after Run.

Пояснение:

  • Функция add принимает три аргумента и возвращает их сумму.
  • Функция curryAdd — это каррированная версия add. Она принимает один аргумент a и возвращает другую функцию, которая принимает b. Эта вторая функция возвращает ещё одну функцию, которая принимает c. Самая внутренняя функция возвращает сумму a, b и c.

Преимущества куррирования

  1. Повторное использование: Каррированные функции позволяют создавать новые функции, фиксируя некоторые аргументы. Это повышает повторное использование, позволяя легко создавать специализированные версии функции без дублирования кода.

Output appears here after Run.

Здесь curriedAdd — это каррированная версия функции add. Она позволяет создавать специализированные функции, такие как add5, фиксируя первый аргумент (a) равным 5. Это способствует повторному использованию кода, поскольку мы можем создавать несколько специализированных функций, не повторяя логику сложения.

  1. Композиция функций: Куррирование упрощает композицию функций. Композиция функций — это процесс объединения двух или более функций для создания новой функции.

Output appears here after Run.

В этом примере мы компонуем функции multiply и addOne с помощью куррирования. Каррируя эти функции, мы можем легко создать новую функцию addOneThenMultiplyBy5, которая сначала прибавляет 1 к входному значению (x), а затем умножает результат на 5. Это показывает, как куррирование облегчает композицию функций, делая создание новых функций путём объединения существующих более простым.

Реализация куррирования в JavaScript

Мы можем создать вспомогательную функцию, чтобы каррировать любую функцию. Вот реализация универсальной функции куррирования:


Output appears here after Run.

Пояснение:

  1. Функция куррирования (curry):

    • Функция curry принимает другую функцию fn в качестве входных данных.
    • Она возвращает новую функцию под названием curried.
    • Эта функция curried принимает любое количество аргументов, используя синтаксис rest-параметра (...args).
  2. Каррированная функция (curried):

    • Внутри curried она проверяет, больше ли или равно количество переданных аргументов (args.length) количеству аргументов, ожидаемых исходной функцией fn (fn.length).
    • Если передано достаточно аргументов, она вызывает исходную функцию fn с этими аргументами, используя fn.apply(this, args).
    • Если аргументов недостаточно, она возвращает новую функцию, которая принимает дополнительные аргументы (nextArgs) с помощью оператора spread (...nextArgs).
    • Эта новая функция рекурсивно вызывает curried с объединёнными аргументами (args.concat(nextArgs)), гарантируя, что все аргументы будут собраны до вызова исходной функции fn.

    Примечание: fn.length учитывает только параметры без значений по умолчанию и игнорирует rest-параметры.

  3. Пример использования:

    • Мы определяем функцию multiply, которая принимает три аргумента и возвращает их произведение.
    • Мы создаём каррированную версию функции multiply, передавая её в функцию curry, которая возвращает новую функцию curriedMultiply.
    • Теперь curriedMultiply можно вызывать с одним, двумя или тремя аргументами.
    • Каждый раз, когда мы вызываем curriedMultiply с одним или несколькими аргументами, она возвращает новую функцию, пока все аргументы не будут собраны, после чего возвращается результат перемножения аргументов.

Изучение частичного применения

Частичное применение — это техника, при которой вы создаёте новую функцию, предварительно подставляя некоторые аргументы исходной функции. Это особенно полезно для создания специализированных функций.

Пример частичного применения

Рассмотрим следующую функцию, которая форматирует сообщение:


javascript
function formatMessage(greeting, name) {
    return `${greeting}, ${name}!`;
}

Мы можем создать частично применённую функцию:


Output appears here after Run.

Пояснение:

  • Функция formatMessage принимает два аргумента, greeting и name, и возвращает отформатированное сообщение.
  • Функция partial принимает функцию fn и некоторые заранее заданные аргументы (...presetArgs). Она возвращает новую функцию, которая принимает оставшиеся аргументы (...laterArgs).
  • Когда новая функция вызывается, она объединяет presetArgs и laterArgs и вызывает исходную функцию fn с этими аргументами.
  • Используя partial, мы создаём greetHello — функцию, которая всегда использует "Hello" в качестве приветствия. При вызове с именем она возвращает полное сообщение.

Преимущества частичного применения

  1. Упрощение: Создание более простых функций из более сложных

    Предположим, у нас есть функция, которая вычисляет итоговую цену товара после применения скидки и налога.


javascript
function calculateFinalPrice(price, discount, tax) {
    return price - (price * discount) + (price * tax);
}

Эта функция требует три аргумента, поэтому её немного неудобно использовать многократно, если ставки скидки и налога часто одинаковы. С частичным применением мы можем упростить это.


Output appears here after Run.

В приведённом выше примере applyDiscountAndTax — это частично применённая функция, которая заранее задаёт значения discount и tax. Это облегчает вычисление итоговой цены для разных товаров без необходимости каждый раз указывать ставки скидки и налога.

  1. Повторное использование кода: Использование общей логики функции с разными заранее заданными аргументами

    Представим, что у нас есть функция, которая выводит сообщения с разными уровнями важности.


javascript
function logMessage(level, message) {
    console.log(`[${level}] ${message}`);
}

Мы можем создать переиспользуемые функции для разных уровней логирования с помощью частичного применения.


Output appears here after Run.

Здесь createLogger — это частично применённая функция, которая задаёт аргумент level. Функции infoLogger и errorLogger теперь можно использовать для вывода сообщений с заранее заданными уровнями логирования, повторно используя общую логику logMessage.

  1. Улучшенная читаемость: Делает код более читаемым, разбивая сложные функции Рассмотрим функцию, которая форматирует даты в разных стилях.

javascript
function formatDate(date, format) {
    const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
    if (format === 'US') {
        options.month = 'long';
    } else if (format === 'EU') {
        options.day = 'numeric';
        options.month = 'numeric';
    }
    return new Date(date).toLocaleDateString(undefined, options);
}

Используя частичное применение, мы можем создавать более читаемые функции для разных форматов дат.


Output appears here after Run.

createDateFormatter частично применяет аргумент format, в результате чего получаются специальные функции для форматов дат US и EU. Такое разбиение делает код более читаемым и понятным, поскольку каждый форматтер предназначен для конкретного формата.

Эти примеры показывают, как частичное применение в JavaScript может упростить сложные функции, повысить повторное использование кода и улучшить читаемость, делая код более удобным для сопровождения и понимания.

Лучшие практики использования куррирования и частичного применения

Сохраняйте функции чистыми

  • Убедитесь, что каррированные и частично применённые функции остаются чистыми, без побочных эффектов. Это облегчает их понимание и тестирование.

Используйте, когда это уместно

  • Используйте куррирование и частичное применение, когда они естественно подходят для решаемой задачи.
  • Избегайте чрезмерного использования этих техник, поскольку при неосторожном применении они могут сделать код труднее для понимания.

Используйте композицию функций

  • Комбинируйте каррированные функции для создания более сложной функциональности. Куррирование хорошо работает с композицией функций, что ведёт к более модульному коду.

Документация и именование

  • Тщательно документируйте каррированные и частично применённые функции, чтобы обозначить ожидаемый способ их использования.
  • Используйте понятные и описательные имена функций, чтобы передать их назначение.

INFO

Если вы часто используете куррирование и частичное применение, рассмотрите библиотеку Lodash, которая предоставляет удобные вспомогательные функции, такие как _.curry и _.partial.

Сочетание с методами массивов

Куррирование можно эффективно сочетать с методами массивов, такими как map, filter и reduce, для лаконичного и выразительного кода.


Output appears here after Run.

Пояснение:

  • Функция curriedMultiply используется для создания multiplyByTwo — функции, которая умножает свой аргумент на 2.
  • Массив numbers преобразуется с помощью map, который применяет multiplyByTwo к каждому элементу, в результате чего получается новый массив удвоенных чисел.

Заключение

Куррирование и частичное применение в JavaScript предлагают мощные техники для упрощения композиции функций, повышения повторного использования кода и улучшения читаемости. Куррирование преобразует функции с несколькими аргументами в последовательность унарных функций, позволяя создавать более гибкий и модульный код. Частичное применение позволяет заранее задавать аргументы функции, облегчая повторное использование кода и упрощение сложных функций. Используя эти концепции функционального программирования, разработчики могут писать более чистый, лаконичный и удобный в сопровождении код на JavaScript.

Practice

What is correct about Currying in JavaScript?

Считаете ли это полезным?

Предпросмотр dual-run — сравните с маршрутами Symfony на продакшене.