Roman Ponomarev
Jun 7, 2017 · 5 min read

Перевод статьи Functional Reactive Ninja: Partial Application of Functions.

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

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

Меня часто спрашивают: «Почему вы бы частично применили функцию?».

«Потому что логика, которую я получаю после этого, — это красота и функциональная чистота».

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

Использование function.bind()

Этот стиль частичного применения не является функционально верным, но я хочу, чтобы вы знали о нем. Пример частичного применения функций с помощью bind():

let add = (a, b) => a+b;let increment = add.bind(null,1);
let incrementBy2 = add.bind(null,2);
console.log('Increment 3 by 2:',incrementBy2(3));
//=> Увеличиваем 3 на 2: 5
console.log('Increment 3 by 1:',increment(3));
//=> Увеличиваем 3 на 1: 4
  1. Мы создали функцию add, принимающую два аргумента.
  2. Мы предварительно применили ее с одним аргументом и создали функцию increment, принимающую только один аргумент.
  3. Мы точно также создали функцию incrementBy2, но применили ее с другим аргументом.
  4. Мы вызвали наши предварительно примененные функции с окончательным аргументом.

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

  • Слишком непредсказуемо: function.bind всегда возвращает другую функцию, даже если мы предоставили все аргументы базовой функции. Поэтому мы не знаем, когда остановиться.
  • Обратите внимание, что в коде используется null - это контекст частично применяемой функции, который мы должны передать в качестве первого аргумента bind. Каждый раз, когда мы частично применяем функцию, мы вынуждены присоединять контекст - не круто!

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

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

let add = x => y => x+y;let increment = add(1);
let incrementBy2 = add(2);
console.log('Increment 3 by 1:',increment(3));
//=> Увеличиваем 3 на 1: 4
console.log('Increment 3 by 2:',incrementBy2(3));
//=> Увеличиваем 3 на 2: 5

Каррирование и связывание

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

Функциональная чистота

Каррированная функция всегда чиста, так как она генерирует одну и ту же функцию для одних и тех же входных данных

Каррирование — это пояс с крутыми фишками Бэтмена для функционального программиста, и мы увидим, насколько оно незаменимо.

Теперь я хочу, чтобы вы взглянули на этот код.

‘Hello’.replace(/Hello/g, ‘Bye’).concat(‘!’);

Эта конструкция называется чейнинг методов (method chaining) и, как известно, это хороший объектно-ориентированный подход к проектированию. Теперь, если вы посмотрите внимательней, вы заметите, как он работает, и чего ему не хватает.

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

Мы можем продолжать, например, так:

‘Hello’
.replace(/Hello/g, ‘Bye’)
.concat(‘!’)
.repeat(2)
.split('!')
.filter(x=>x!='!')
.map(x=>'Hello').toString();

Вышеупомянутая конструкция возможна из-за объекта Hello: все методы в цепочке бесполезны без объекта, предоставляющего их. Это грустно. Проблема конкретно здесь и с каждым объектно-ориентированным подходом к проектированию, что все крутится вокруг объектов, все зависит от данных.

Давайте перейдем к функциональному подходу:

const replace = (regex,replacement,str) => str.replace(regex,replacement);const concat = (item,str) => str.concat(item);concat('!',replace(/Hello/g,'Bye','Hello'));

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

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

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

const compose = (...fns) => x => fns.reduce((v, fn) => fn(v), x);

Давайте объединим наши функции, но, подождите, у нас есть проблема: наша функция-композер работает с функциями, которые принимают только один параметр, а наши функции replace и concat явно принимают более одного параметра.

Время для нашей любимой техники — каррирования:

const replace = (regex,replacement) => str => str.replace(regex,replacement);const concat = item => str => str.concat(item);

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

В итоге:

compose(replace(/Hello/g,’Bye’),concat(‘!’))(‘Hello’)

Мы можем продолжать, например, так:

compose(
replace(/Hello/g,’Bye’),
concat(‘!’),
repeat(2),
split('!'),
filter(x=>x!='!'),
map(x=>'Hello'),
toString
)(‘Hello’)
// илиprocessHello(‘Hello’)

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

[‘Hello’,’Hello world’,’Hi’].map(processHello)

Я извиняюсь, я имел ввиду вот так:

map(processHello)(‘Hello’,’Hello world’,’Hi’)

Поздравляю, мы только что написали функцию в бесточечном стиле. Вы не видите «.».

Функции в бесточечном стиле — функции, не упоминающие данные, которыми они оперируют. Этот стиль записи функций называется бесточечным стилем (point-free) или комбинаторным программированием (tacit programming). Согласно Википедии:

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

Каррирование и композиция очень хорошо подходят для программирования в таком стиле.

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

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

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

Представьте себе Бэтмена без его пояса. Функциональный Javascript просто невозможен без каррирования, поэтому популярные библиотеки функционального программирования, такие как lodash/fp или Ramda, поставляются с уже каррированными функциями.

Написано 💖.

Спасибо за прочтение.


Слушайте наш подкаст в iTunes и SoundCloud, читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook.

Статья на GitHub

devSchacht

Подкаст. Переводы. Веб-разработка.

Roman Ponomarev

Written by

arrival.com developer, youknow.st activist, medium.com/maria-machine author; github.com/maksugr

devSchacht

Подкаст. Переводы. Веб-разработка.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade