Функциональное программирование в помощь?

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

Часть 1/7. Почему реактивное программирование такое сложное?
Часть 2/7. Функциональное программирование спешит на помощь?
Часть 3/7 Почему функциональное программирование вызывает такие споры?
Часть 4/7 Функциональное реактивное программирование: простое и единодушное?
Часть 5/7 Как возникло функциональное реактивное программирование?
Часть 6/7. Где сейчас функциональное реактивное программирование?
Часть 7/7. Какое функциональное реактивное программирование является Граалем?

Предупреждение: эта часть серии большая . Я надеюсь, что он будет достаточно большим, чтобы убедить невежд, сомневающихся, неверующих, скептиков и неверных! Впереди тяжелая работа…

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

1. Чистые функции

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

1.1. Ссылочная прозрачность означает отсутствие побочных эффектов

Пример нечистой функции JavaScript:

function increment(){
  count++; // side-effect
}
increment();

Пример чистой функции JavaScript:

var count = 0;
function increment(count){
  return count + 1;
}
var incrementedCount = increment(counter);

1.2. Ссылочная прозрачность означает детерминизм

Результат функции зависит только от аргумента функции (так называемая контекстно-независимость).

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

Так называемая оптимизация запоминания.

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

Обратите внимание, что это на самом деле не относится к JavaScript, поскольку функция всегда возвращает значение, которое по умолчанию имеет тип undefined.

Должен ли я заботиться или не должен?

Многие ошибки происходят из-за того, что побочные эффекты выходят из-под контроля.

Эти ошибки, как правило, трудно определить точно.

Вы хотите не тратить свое время и не рвать на себе волосы на их отладку (если, конечно, вы не настоящий мазохист, которым вы имеете полное право быть, конечно).

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

2. Функционируйте как первоклассные граждане

2.1. Вы можете передавать функции в качестве аргументов другой функции (называемой функцией «высшего порядка»).

Пример обратного вызова JavaScript:

function formatTitle(text){ // callback function
  return text.toUpperCase();
}
function generateTitle(words, format){ // higher-order function
  return format(words.join(" "));
}
var title = generateTitle(["Simple", "Unanimous", "Functional", "Reactive", "Programming"], formatTitle);

2.2. Вы можете вернуть функцию как результат функции (также называемой функцией «высшего порядка»)

Пример каррированной функции JavaScript (каррированная функция — это функция с одним аргументом, которая удобна для композиции функций):

function sum(a, b){ // function taking two arguments
  return a + b;
}
// curry (defined elsewhere) is a higher-order function
// returning a function
// increment is a function taking a 
// single argument (curried function)
var increment = curry(sum, 1); 
var count = 0;
var incrementedCount = increment(count); // 1

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

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

Пример реализации функции curry, показанный ранее, с использованием замыкания (т. важный аспект функционального программирования, который я не буду здесь объяснять) и синтаксисы ES6 стрелка и расширение (альтернативы старому синтаксису JavaScript, которые я также не буду здесь объяснять):

// returns a single-argument function from a multi-argument function
// the missing argument values are taken from the closure
function curry(multiArgumentFunction, …args){
  return arg => multiArgumentFunction(arg, …args);
}

Должен ли я заботиться или не должен?

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

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

Более того, JavaScript прекрасно справляется с этим (да, я люблю JavaScript. И что?).

3. Декларативное программирование против процедурного программирования

В декларативном программировании вы указываете, что вы хотите, тогда как в процедурном программировании вы указываете, как вы получаете то, что хотите.

Типичный пример — сопоставление массива:

// procedural programming
for(var i = 0; i < array.length; i++){
  array[i] = array[i] * array[i]; 
} 
// declarative programming
array.map(function(item){
  return item * item; 
});

Должен ли я заботиться или не должен?

Фильтр массива, карта и редукция довольно модны в наши дни. Вы уверены, что хотите остаться позади?

Декларативный стиль более лаконичен, более выразителен (сфокусирован на том, что важно), менее подвержен ошибкам.

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

4. Неизменность

Функциональное программирование часто ассоциируется с понятием неизменяемость. Неизменяемость вытекает из концепции ссылочной прозрачности, рассмотренной в параграфе 2.1.

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

Должен ли я заботиться или не должен?

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

JavaScript — это мультипарадигменный язык, который поддерживает декларативное программирование, а также процедурное программирование. Большинство встроенных функций, работающих с объектами и массивами, не изменяют свои аргументы (map, filter, reduce, concat, разрезать, …), но некоторые другие (сортировать, склеивать, проталкивать, выталкивать, …). Вам лучше знать, какой из них использовать. РТФМ! В некоторых случаях может потребоваться сначала скопировать структуру данных:

// slice() without argument copies the array so 
// in-place sort() does not alter the original array
return array.slice().sort();

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

5. Комбинаторы функций

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

Комбинаторы — это удобный способ объединения функций в новые функции. Вот пример использования комбинатора композиции функций, где increment и double — это функции, а compose — комбинатор:

var doubleAndIncrement = compose(increment, double);
var x = doubleAndIncrement(10); // 21

Комбинатор может даже генерировать комбинаторы. Просто ради интереса, проверьте знаменитый (по крайней мере, в сфере лямбда-исчисления) рекурсивный строгий Z-комбинатор с фиксированной точкой в ​​JavaScript!

Должен ли я заботиться или не должен?

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

Уровень человеческого сознания (медленно) повышается как и уровень абстракции в программировании. "Такова жизнь"!

6. Бесточечный стиль

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

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

// point-full style: x is an element of the g function domain
function FoG(x){ return f(g(x)); } 
// point-free style: using the compose function is a combinator, 
// the x element is not needed
let FoG = compose(f, g);

Бесточечный стиль относится к программированию на уровне функций, а не к программированию на уровне значений (две парадигмы программирования, определенные Джоном Бэкусом в 1977 г.).

программирования» (обычно языки стекового программирования, использующие обратную польскую нотацию, такую ​​как Forth, PostScript и Joy).

Должен ли я заботиться или не должен?

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

Бесточечный стиль идет рука об руку с комбинаторами и особенно с функциональной композицией.

7. Лямбда-исчисление

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

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

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

λf.(λx.f(λy.x x y))(λx.f(λy. x x y))

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

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

Должен ли я заботиться или не должен?

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

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

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

Важно знать названия птиц!

8. Сигнатура типа функции

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

Часто в функциональном программировании широко используется сигнатура типа функции.

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

Наиболее распространенным формализмом является формализм Хиндли – система типа Милнера. В качестве примера, вот сигнатура типа функции функции JavaScript reduceArray (стрелка представляет функцию, буквы обозначают общие типы, квадратные скобки представляют массив универсального типа, а круглые скобки устраняют неоднозначность). приоритет):

// reduce :: (b -> a -> b) -> b -> [a] → b

Он гласит: reduce — это функция, возвращающая значение типа b и принимающая три аргумента, первый аргумент — это функция, возвращающая значение типа b > и принимая аргумент типа b и аргумент типа a, второй аргумент является значением типа b, третий аргумент массив типа a.

Существуют и другие формализмы, такие как Алгебраические типы данных, которые допускают рекурсивную композицию типов (произведение типа — это кортеж, а сумма типов — это объединение в более классической системе типов) и Обобщенный алгебраический тип данных. Типы данных» (подпись типа функции рассматривается как функция типа, которая может применяться к типам для генерации типов).

Должен ли я заботиться или не должен?

Хотя создание, понимание и поддержка сигнатур типов функций требуют времени, терпения и самопожертвования, вы обнаружите (по крайней мере, я), что польза намного превосходит боль, поскольку помогает рассуждать на более высоком уровне абстракции.
< br /> Обратите внимание, что некоторые функциональные языки имеют свободную типизацию (JavaScript) или вообще не типизируют (исходное лямбда-исчисление). Однако вы можете использовать сигнатуры типов (в комментариях к коду или на бумаге), чтобы помочь своим рассуждениям.

Если вы считаете, что типизированные языки превосходят нетипизированные языки (я думаю, потому что это помогает предотвратить ошибки), подумайте: минуту о следующем: нетипизированное лямбда-исчисление является полным по Тьюрингу, а типизированное лямбда-исчисление — нет.

Однажды утром вы просыпаетесь и видите, что все ваши прекрасные файлы JavaScript .js исчезли, их заменили эти уродливые файлы . ts TypeScript: не паникуйте, вы мастер подписи шрифтов!

9. Теория категорий

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

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

Теория категорий в целом сформулирована очень простыми, очень мощными и общими понятиями.

Должен ли я заботиться или не должен?

Как уже упоминалось, фильтр массива , map и reduce сейчас в моде. Все это и многое другое является частью теории категорий.

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

Будьте осторожны при глубоком погружении в эзотерические концепции теории категорий:

Монада в категории X — это моноидный объект в моноидальной категории эндофункторов X с моноидальной структурой, заданной композицией.

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

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

Об авторе
Николас Румянцев — член команды разработчиков в одном из 10 скрам-групп R&D в Esker, французской SaaS-компании, где он спроектировал и внедрил несколько JavaScript-фреймворков на стороне клиента для веб-приложений с широкими возможностями настройки.
Ему нравится музыка, JavaScript (он, возможно, уже говорил вам об этом) и планета Земля.
Его технические супергерои:
Альберт Эйнштейн, который показал, что вы можете достичь удивительных открытий дешевый эксперимент, задолго до того, как консоль браузера окажется у вас под рукой (F12),
Эндрю Уайлс, показавший, что можно достичь своей самой невероятной мечты, даже если первая попытка не удалась,
Алан Тьюринг, который показал, что можно доказать недоказуемое,
Григорий Перельман, показавший, что опыт в ракетостроении не мешает быть специалистом в ракетном образе жизни,
Брендан Эйх, показавший, что такой большой огромный воздействие может исходить от такой крошечной вещицы.