Статьи

Руководство для начинающих по карри в функциональном JavaScript

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

Более читаемый и гибкий

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

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

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

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

Карринг элементален в таких языках, как Haskell и Scala, которые построены вокруг функциональных концепций. У JavaScript есть функциональные возможности, но карри не встроен по умолчанию (по крайней мере, в текущих версиях языка). Но мы уже знаем некоторые функциональные приемы, и мы можем заставить карри работать за нас и в JavaScript.

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

var greet = function(greeting, name) {
  console.log(greeting + ", " + name);
};
greet("Hello", "Heidi"); //"Hello, Heidi"

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

Наше первое карри

 var greetCurried = function(greeting) {
  return function(name) {
    console.log(greeting + ", " + name);
  };
};

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

 var greetHello = greetCurried("Hello");
greetHello("Heidi"); //"Hello, Heidi"
greetHello("Eddie"); //"Hello, Eddie"

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

 greetCurried("Hi there")("Howard"); //"Hi there, Howard"

Почему бы не попробовать это в вашем браузере?

Карри Все Вещи!

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

 var greetDeeplyCurried = function(greeting) {
  return function(separator) {
    return function(emphasis) {
      return function(name) {
        console.log(greeting + separator + name + emphasis);
      };
    };
  };
};

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

 var greetAwkwardly = greetDeeplyCurried("Hello")("...")("?");
greetAwkwardly("Heidi"); //"Hello...Heidi?"
greetAwkwardly("Eddie"); //"Hello...Eddie?"

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

 var sayHello = greetDeeplyCurried("Hello")(", ");
sayHello(".")("Heidi"); //"Hello, Heidi."
sayHello(".")("Eddie"); //"Hello, Eddie."

И мы можем определить подчиненные варианты так же легко:

 var askHello = sayHello("?");
askHello("Heidi"); //"Hello, Heidi?"
askHello("Eddie"); //"Hello, Eddie?"

Карри традиционные функции

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

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

 var curryIt = function(uncurried) {
  var parameters = Array.prototype.slice.call(arguments, 1);
  return function() {
    return uncurried.apply(this, parameters.concat(
      Array.prototype.slice.call(arguments, 0)
    ));
  };
};

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

 var greeter = function(greeting, separator, emphasis, name) {
  console.log(greeting + separator + name + emphasis);
};
var greetHello = curryIt(greeter, "Hello", ", ", ".");
greetHello("Heidi"); //"Hello, Heidi."
greetHello("Eddie"); //"Hello, Eddie."

И, как и прежде, мы не ограничены количеством аргументов, которые мы хотим использовать при построении производных функций из нашей исходной карри-функции:

 var greetGoodbye = curryIt(greeter, "Goodbye", ", ");
greetGoodbye(".", "Joe"); //"Goodbye, Joe."

Серьезно относиться к карри

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

Некоторые функциональные библиотеки JavaScript, такие как Ramda, имеют более гибкие функции каррирования, которые могут разбивать параметры, необходимые для функции, и позволяют передавать их по отдельности или в группах для создания пользовательских карри-вариантов. Если вы хотите широко использовать карри, это, вероятно, путь.

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

Аргумент Порядок

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

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

Вывод

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

Если вам понравился этот пост, вам также могут понравиться некоторые другие из этой серии: