Итак, вы хотите научиться функциональному программированию (Часть 6)

Перевод статьи Charles Scalfani: So You Want to be a Functional Programmer (Part 6) с наилучшими пожеланиями от автора.

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

Предыдущие части: Часть 1, Часть 2, Часть 3, Часть 4, Часть 5.

Что теперь?

Теперь, когда вы изучили весь этот новый замечательный материал, вы, возможно, подумаете: «И что теперь? Как мне использовать это в моём обычном коде?».

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

Если же вы умеете программировать только на императивном языке типа JavaScript (на уровне, которым мы все должны владеть), тогда вы можете продолжить использовать всё, что уже изучили, но теперь дисциплинируя себя.

Функциональный JavaScript

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

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

Неизменяемость

Первая вещь, принимаемая во внимание — это неизменяемость. В ES2015 или ES6, как он ещё называется, появилось новое ключевое слово для объявления переменных — const. Оно означает, что если переменная была установлена однажды, она не может быть переопределена:

const a = 1;
a = 2; // выбросит TypeError в Chrome, Firefox или Node
// но не в Safari (на момент 10/2016)
// (прим. пер., в Safari всё хорошо с версии 10.1)

Здесь a определена как константа и по этой причине не может быть изменена после установки. Вот почему выражение a = 2 выбрасывает ошибку.

Проблема const в том, что в JavaScript она не заходит в своей идее так далеко, как должна. Следующий пример проиллюстрирует её предел:

const a = {
x: 1,
y: 2
};
a.x = 2; // НЕТ ИСКЛЮЧЕНИЯ!
a = {}; // а вот это выбросит TypeError

Заметьте, что a.x = 2 НЕ выбрасывает исключения. Единственное значение, остающееся неизменяемым с const – это сама переменная a. Всё, что a в себе определяет, может быть изменено.

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

Как же мы можем достичь полной неизменяемости в JavaScript?

К сожалению, это возможно только с помощью библиотеки Immutable.js. Она должна дать нам должный уровень неизменяемости, но, увы, её использование также сделает наш код больше похожим на Java, чем на JavaScript.

Каррирование и композиция

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

const f = a => b => c => d => a + b + c + d;

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

И, чтобы вызвать f, мы должны написать:

console.log(f(1)(2)(3)(4)); // выведет 10

Но круглых скобок здесь достаточно, чтобы заставить плакать Lisp-программиста.

Существует множество библиотек, облегчающих задачу каррирования. Одна из моих любимых — Ramda.

Использование Ramda теперь позволяет нам написать:

const f = R.curry((a, b, c, d) => a + b + c + d);
console.log(f(1, 2, 3, 4)); // выведет 10
console.log(f(1, 2)(3, 4)); // также выведет 10
console.log(f(1)(2)(3, 4)); // также выведет 10

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

Используя Ramda, мы можем переписать функцию mult5AfterAdd10 из Части 3 и Части 4:

const add = R.curry((x, y) => x + y);
const mult5 = value => value * 5;
const mult5AfterAdd10 = R.compose(mult5, add(10));

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

const mult5AfterAdd10 = R.compose(R.multiply(5), R.add(10));

map, filterreduce

Ramda также имеет свои собственные версии map, filter и reduce. Несмотря на то, что эти функции находятся в Array.prototype в нативном JavaScript, их версии в Ramda каррированные:

const isOdd = R.flip(R.modulo)(2);
const onlyOdd = R.filter(isOdd);
const isEven = R.complement(isOdd);
const onlyEven = R.filter(isEven);
const numbers = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(onlyEven(numbers)); // выводит [2, 4, 6, 8]
console.log(onlyOdd(numbers)); // выводит [1, 3, 5, 7]

R.modulo принимает два параметра. Первый – это делимое (то, что мы делим), второй – делитель (то, на что мы делим).

Функция isOdd возвращает остаток от деления на два. При остатке от нуля чётного числа возвращается ложь, при остатке от единицы нечётного числа – истина. Мы перебросили (прим. пер., flip) первый и второй параметр из modulo, поэтому смогли установить 2, как делитель.

Функция isEven – просто обратная (прим. пер., complement) версия isOdd.

Функция onlyOddфильтрующая функция с предикатом (функцией, возвращающей булево значение) isOdd. Перед выполнением она ожидает последнее значение в переданном списке чисел.

Функция onlyEvenфильтр, использующий isEven в качестве предиката.

Когда мы передаём number в onlyEven и в onlyOdd, isEven и isOdd получают свои последние параметры и могут наконец-то выполниться, возвратив ожидаемые числа.

Недостатки JavaScript

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

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

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

Одним из первым таких языков был CoffeeScript. Сейчас на вооружение Angular 2 (прим. пер., «сейчас» актуально для Angular 5) был принят TypeScript. Транспилятором для JavaScript является Babel.

Всё больше и больше людей прибегают к такому подходу в продакшене.

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

Elm

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

Но что такое Elm? И как мне использовать его?

Elm — это чистый функциональный язык, компилируемый в JavaScript, поэтому вы можете использовать его для создания веб-приложений, используя The Elm Architecture, также называемую TEA (эта архитектура вдохновила разработчиков Redux).

У приложений, написанных на Elm, НЕ возникает никаких ошибок выполнения.

Elm используется в продакшене такими компаниями, как NoRedInk, где Эван Чаплики, создатель Elm, сейчас работает (раньше он работал в Prezi).

Послушайте монолог 6 Months of Elm in Production от Ричарда Фельдмана, Elm-евангелиста из NoRedInk, для более подробной информации.

Должен ли я заменить весь свой JavaScript на Elm?

Нет. Вы можете постепенно заменять части кода. Чтобы узнать побольше, загляните в статью How to use Elm at Work из блога Elm.

Зачем учить Elm?

  1. Программирование на чистом функциональном языке несёт в себе одновременно ограничение и освобождение. Ограничение заключается в ограничении ваших действий (зачастую, это защита вас от вас самих), но в тоже время вы освобождаетесь от ошибок и плохих архитектурных решений, так как Elm-приложения следуют The Elm Architecture, функционально-реактивной модели.
  2. Функциональное программирование усовершенствует ваши навыки программиста. Все вышеупомянутые идеи — лишь верхушка айсберга. Вам действительно стоит увидеть их практическую реализацию, чтобы оценить насколько ваши программы уменьшатся в размере и вырастут в показателях стабильности.
  3. JavaScript был изначально написан за десять дней и затем пропатчен в течение двух следующих десятков дней, чтобы превратиться в немного функциональный, немного объектно-ориентированный и полноценный императивный язык программирования. Elm был разработан с учётом накопленного за тридцать лет опыта развития Haskell-сообщества, опирающегося на десятилетия работы в области математики и информатики. The Elm Architecture (TEA) планировалась и улучшалась на протяжении многих лет и стала результатом личного достижения Эвана в функциональном реактивном программировании. Посмотрите Controlling Time and Space, чтобы оценить уровень мышления, сформулировавшего эту архитектуру.
  4. Elm был задуман для фронтенд-разработчиков. Он стремится сделать их жизнь проще. Посмотрите Let’s Be Mainstream, чтобы лучше понимать эту цель.

Будущее

Невозможно предугадать, что принесёт с собой завтрашний день, но мы можем произвести некоторые грамотные предположения. Вот некоторые из моих:

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

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

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

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

Я надеюсь, что после прочтения этих статей, вы чувствуете себя более уверенными в своих способностях и понимаете эти идеи.

Желаю вам удачи в ваших будущих начинаниях.


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

Статья на GitHub