Вы когда-нибудь слышали о «каррировании» в парадигме языков программирования? Нет, это не индийский рецепт, но он определенно может сделать ваш код вкуснее.

Независимо от того, сталкивались ли вы с замыканиями и каррированием раньше или вы новичок в этом, в этом руководстве вы узнаете о:

  • Разница между замыканиями и каррированием
  • Каррирование и его основные преимущества
  • Почему вы должны использовать каррирование в своих проектах

Я дам вам теорию, а также действительные варианты использования, примеры и солидный математический фон.

Отказ от ответственности: я буду основывать эту статью на JavaScript, однако основная идея может быть применена к любому современному языку программирования.

Что такое замыкания?

Замыкание можно описать как комбинацию функции и области видимости, в которой она была объявлена.

Но что конкретно это означает? Область действия состоит из любых локальных переменных в окружении функции при ее создании. Замыкание позволяет обращаться ко всем локальным переменным функции в том состоянии, в котором они были найдены.

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

Вот пример:

const closuredFunction= someVariable =>{
   let scopedVariable=someVariable;
   const closure=()=>{
       scopedVariable++;
       console.log(scopedVariable);
   }
  
   return closure;
}

Примечание. Я принял someVariable за целое число (из-за ++), но его можно экстраполировать на любой другой тип переменной. Я буду продолжать использовать стрелочные функции на протяжении всей этой статьи, если вам нужны дополнительные пояснения, просто напишите комментарий, и я рефакторинг его.

Затворы: практическое применение

До появления классов в ES6 замыкания представляли собой простой способ создания классовой конфиденциальности, сравнимый с тем, который используется в ООП (Oобъектно Oориентированный Pпрограммирование), позволяющее эмулировать приватные методы. Это известно как «модульный шаблон», и он позволяет нам писать легко поддерживаемый код с уменьшенным пространством имен загрязнениеми другими возможностями повторного использования.

Продолжая приведенный выше пример, внешняя функция (closuredFunction) является общедоступной функцией, которая имеет доступ к некоторым закрытым переменным (scopedVariable) и внутренним функциям (closure).

Теперь применим следующее:

const closuredFunction= someVariable =>{
   let scopedVariable=someVariable;
   const closure=()=>{
       scopedVariable++;
       console.log(scopedVariable);
   }
  
   return closure;
}
let testOne = closuredFunction(1);
testOne(); // will output 2
testOne(); // will output 3
let testTwo = closuredFunction(10);
testTwo(); // will output 11
testTwo(); // will output 12
testOne(); // will output 4

Что случилось? Все вызовы testOne() обращаются к одной и той же внешней области, поэтому один и тот же scopedVariable. Если одно изменится, следующее значение изменится соответственно.

Однако другой вызов той же внешней функции создает новую область с новым scopedVariable, поэтому будет запрошена полностью независимая переменная из-за области действия и контекста.

Что такое карри?

C̶u̶r̶r̶y̶ ̶i̶s̶ ̶a̶ ̶v̶a̶r̶i̶e̶t̶y̶ ̶o̶f̶ ̶d̶i̶s̶h̶e̶s̶ ̶o̶r̶i̶g̶i̶n̶a̶t̶i̶n̶g̶ ̶i̶n̶ ̶t̶h̶e̶ ̶I̶n̶d̶i̶a̶n̶ ̶s̶u̶b̶c̶o̶n̶t̶i̶n̶e̶n̶t̶ ̶t̶h̶a̶t̶ ̶u̶s̶e̶ ̶a̶ ̶c̶o̶m̶p̶l̶e̶x̶ ̶c̶o̶m̶b̶i̶n̶a̶t̶i̶o̶n̶ ̶o̶f̶ ̶s̶p̶i̶c̶e̶s̶ ̶o̶r̶ ̶h̶e̶r̶b̶s̶.

Хорошо, одной шутки с карри на статью достаточно.

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

Или другими словами:

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

Каррированные функции создаются путем объединения замыканий в цепочку и немедленного одновременного возврата их внутренних функций.

Как использовать каррирование

Стандартный вызов функции может выглядеть так:

sampleFunction('param1', 'param2', 'param3');

Каррированная функция может выглядеть так:

sampleFunction('param1')('param2')('param3');

Если это выглядит знакомо, то это действительно потому, что HOC (High-Oкомпонент O) является каррированной функцией.

Перевод предыдущего фрагмента в каррированную функцию будет выглядеть так:

function sampleFunction(param1){ 
   //some logic
   return param2 => { 
     //some other logic 
     return param3 => { 
        return finalResult;    
     }  
   }
}

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

Как?

let someParam = sampleFunction(param1);
let anotherParam = someParam(param2);
console.log(anotherParam(param3));

Или в неочищенном виде:

sampleFunction('param1')('param2')('param3');

Это означает, что независимо от того, как он вызывается, если последовательность правильная, окончательный результат всегда будет возвращаться так, как ожидалось.

Является ли каррирование формой закрытия?

Да, как вы, возможно, уже заметили, эти два шаблона имеют сходство. Однако у них различные варианты использования.

Каррирование означает, что замыкание не должно получать все свои аргументы сразу, а по отдельности.

Я нашел эту полезную метафору в Интернете:

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

Каковы преимущества карри? Когда я должен его использовать?

По мере роста вашего проекта вы скоро поймете, насколько полезным может быть каррирование, поскольку оно чрезвычайно масштабируемо. Чем сложнее проект, тем большим спасением окажется каррирование.

Если вы хотите сохранить контроль над большими и сложными приложениями, вы можете использовать каррирование как безопасный метод, обеспечивающий чистый код.

Каррирование и чистый код

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

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

Если ваши функции перегружены (то есть, если у вас много побочных эффектов), ваш код не будет легким и чистым. И если ваш код не чист, ваш проект не масштабируется и его сложно поддерживать.

В идеале функции должны получать всего 1 параметр.

Популярность замыканий упала с тех пор, как JavaScript включил классы в ES6. Однако замыкания и каррирование по-прежнему могут быть важной частью чистого, масштабируемого кода. В функциональном программировании они служат той же цели, что и приватные методы в объектно-ориентированном программировании.

Теперь вы знаете, что такое замыкания и каррирование, как их использовать и зачем. Большинство людей могут остановиться на этом и заняться программированием, но если вы немного чудак, как я, вот дополнительное математическое удовольствие из Википедии для вашего развлечения.

Математическая основа

В математике и информатике каррирование — это метод преобразования функции, которая принимает несколько аргументов, в последовательность функций, каждая из которых принимает один аргумент. Например, каррирование функции

Или, более абстрактно, функция, которая принимает два аргумента, один из X и один из Y, и возвращает Zрезультат каррирования преобразуется в функцию, которая принимает один аргумент из X и выдает в качестве выходных данных функции от X до Z.

Каррирование позволяет работать с функциями, принимающими несколько аргументов, и использовать их в средах, где функции могут принимать только один аргумент. Например, некоторые аналитические методы можно применять только к функциям с одним аргументом. Практические функции часто принимают больше аргументов, чем это. Фреге показал, что достаточно предоставить решения для случая с одним аргументом, поскольку вместо этого можно преобразовать функцию с несколькими аргументами в цепочку функций с одним аргументом. Это преобразование — процесс, теперь известный как каррирование. Все обычные функции, которые обычно встречаются в математическом анализе или в компьютерном программировании, могут быть каррированы. Однако есть категории, в которых каррирование невозможно; наиболее общие категории, допускающие каррирование, — это замкнутые моноидальные категории.

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

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