Функциональное программирование на javascript как улучшить код javascript программ атенсио луис

Обновлено: 02.07.2024

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

Все книги представленные на сайте WEB-Программист только в ознакомительных целях. Любое их использование Вами допускается только в ознакомительных целях. Если Вы планируете их использовать в дальнейшем, то Вы обязаны приобрести их у правообладателей. Администрация сайта не несет ответственность за их использование Вами

Все книги представленные на сайте WEB-Программист только в ознакомительных целях. Любое их использование Вами допускается только в ознакомительных целях. Если Вы планируете их использовать в дальнейшем, то Вы обязаны приобрести их у правообладателей. Администрация сайта не несет ответственность за их использование Вами

Все книги представленные на сайте WEB-Программист только в ознакомительных целях. Любое их использование Вами допускается только в ознакомительных целях. Если Вы планируете их использовать в дальнейшем, то Вы обязаны приобрести их у правообладателей. Администрация сайта не несет ответственность за их использование Вами


Смотри также:

Изучаем TypeScript 3, PDF, 2019

TypeScript — это язык программирования, разработанный Андерсом Хейлсбергом, основателем языка .

Основы разработки веб-приложений

Благодаря этой книге вы усвоите основы создания веб-приложений, построив простое .

Описание: Незаменимая вводная книга по технологии React для взыскательных JavaScript-разработчиков. .

Если вы хотите полностью реализовать потенциал JavaScript, то крайне важно .

Современный учебник JavaScript [3 книги], PDF, 2019

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

image

Хочу подчеркнуть: в статье сделан упор на том, ЗАЧЕМ нужна фича Х, а не на том, ЧТО такое фича Х.

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

ФП — это стиль написания программ, при котором просто комбинируется набор функций. В частности, ФП подразумевает обёртывание в функции практически всего подряд. Приходится писать много маленьких многократно используемых функций и вызывать их одну за другой, чтобы получить результат вроде (func1.func2.func3) или комбинации типа func1(func2(func3())).

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

Проблемы ФП

Если всё можно сделать путём комбинирования набора функций, то…

  1. Как обрабатывать условие if-else? (Подсказка: монада Either)
  2. Как обрабатывать исключения Null? (Подсказка: монада Maybe)
  3. Как удостовериться, что функции действительно многократно используемые и могут использоваться везде? (Подсказка: чистые функции (Pure functions), ссылочная прозрачность (referential transparency))
  4. Как удостовериться, что данные, передаваемые нами в функции, не изменены и могут использоваться и в других местах? (Подсказка: чистые функции (Pure functions), неизменяемость)
  5. Если функция берёт несколько значений, но при объединении в цепочку (chaining) можно передавать только по одной функции, то как нам сделать эту функцию частью цепочки? (Подсказка: каррирование (currying) и функции высшего порядка)
  6. И многое другое <добавьте сюда ваш вопрос>.

ФП-решение

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

Спецификации Fantasy Land и ФП-библиотеки

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

Яркий пример — спецификации Fantasy Land, объясняющие, как должна себя вести каждая JS-функция/класс.

image

На иллюстрации изображены все спецификации и их зависимости. Спецификации — по сути законы, они аналогичны интерфейсам в Java. С точки зрения JS спецификации можно рассматривать как классы или функции-конструкторы, реализующие в соответствии со спецификацией некоторые методы (вроде map, of, chain и т. д.).

JS-класс — это функтор (Functor), если он реализует метод map. И метод должен работать так, как предписано спецификацией (объяснение упрощённое, правил на самом деле больше).

JS-класс — это функтор Apply (Apply Functor), если он в соответствии со спецификацией реализует функции map и ap.

JS-класс — это монада (Monad Functor), если он реализует требования Functor, Apply, Applicative, Chain и самой Monad (в соответствии с цепочкой зависимостей).

Примечание: зависимость может выглядеть как наследование, но необязательно. Например, монада реализует обе спецификации — Applicative и Chain (в дополнение к остальным).

Библиотеки, совместимые со спецификациями Fantasy Land

Есть несколько библиотек, реализующих спецификации FL. Например: monet.js, barely-functional, folktalejs, ramda-fantasy (на базе Ramda), immutable-ext (на базе ImmutableJS), Fluture и др.

Какие библиотеки мне лучше использовать?

Библиотеки наподобие lodash-fp и ramdajs позволят только начать писать в стиле ФП. Но они не предоставляют функции для использования ключевых математических концепций вроде монад, функторов или редьюсера (Foldable), позволяющих решать реальные проблемы.

Так что я бы вдобавок порекомендовал выбрать одну из библиотек, использующих спецификации FL: monet.js, barely-functional, folktalejs, ramda-fantasy (на базе Ramda), immutable-ext (на базе ImmutableJS), Fluture и т. д.

Примечание: я пользуюсь ramdajs и ramda-fantasy.

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

Пример 1. Работа с проверками на Null

В разделе рассматриваются: функторы, монады, монады Maybe, каррирование

Применение: мы хотим показывать разные начальные страницы в зависимости от пользовательской настройки предпочтительного языка. Нужно написать getUrlForUser, возвращающий соответствующий URL из списка URL’ов (indexURL’ы) для пользовательского (joeUser) предпочтительного языка (испанский).

image

Проблема: язык не может быть null. И пользователь тоже не может быть null (не залогинен). Языка может не быть в нашем списке indexURL’ов. Поэтому нам нужно позаботиться о многочисленных nulls или undefined.

Решение (императивное против функционального):

Не переживайте, если ФП-версия выглядит трудной для понимания. Дальше в этой статье мы разберём её шаг за шагом.

Давайте сначала разберём ФП-концепции и методики, использованные в этом решении.

Функторы

Любой класс (или функция-конструктор) или тип данных, хранящий значение и реализующий метод map, называется функтором (Functor).

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

Напишем собственный функтор — MyFunctor. Это просто JS-класс (функция-конструктор), который хранит какое-то значение и реализует метод map. Этот метод применяет функцию к хранимому значению, а затем из результата создаёт новый Myfunctor и возвращает его.

P. S. Функторы должны реализовывать и другие спецификации (Fantasy-land) в дополнение к map, но здесь мы этого касаться не будем.

Монады

Монады тоже функторы, т. е. у них есть метод map. Но они реализуют не только его. Если вы снова взглянете на схему зависимостей, то увидите, что монады должны реализовывать разные функции из разных спецификаций, например Apply (метод ap), Applicative (методы ap и of) и Chain (метод chain).

image

Упрощённое объяснение. В JS монады — это классы или функции-конструкторы, хранящие какие-то данные и реализующие методы map, ap, of и chain, которые что-то делают с хранимыми данными в соответствии со спецификациями.

Вот образец реализации, чтобы вы понимали внутреннее устройство монад.

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

Монада Maybe

Монада Maybe — это класс, реализующий спецификацию монады. Но особенность монады в том, что она корректно обрабатывает значения null или undefined.

В частности, если хранимые данные являются null или undefined, то функция map вообще не выполняет данную функцию, потому не возникает проблем с null и undefined. Такая монада используется в ситуациях, когда приходится иметь дело с null-значениями.

В коде ниже представлена ramda-fantasy реализация монады Maybe. В зависимости от значения она создаёт экземпляр одного из двух разных подклассов — Just или Nothing (значение или полезное, или null/undefined).

Хотя методы Just и Nothing одинаковы (map, orElse и т. д.), Just что-то делает, а Nothing не делает ничего.

Обратите особое внимание на методы map и orElse в этом коде:

Давайте посмотрим, как можно использовать монаду Maybe для работы с проверками на null.

  1. Если есть какой-то объект, который может быть null или иметь null-свойства, то создаём из него объект-монаду.
  2. Применим библиотеки наподобие ramdajs, которые используют Maybe для доступа к значению изнутри и снаружи монады.
  3. Предоставим значение по умолчанию, если реальное значение окажется null (т. е. заранее обработаем null-ошибки).

Currying (помогает работать с глобальными данными и мультипараметрическими функциями)

В разделе рассматриваются: чистые функции (Pure functions) и композиция (Composition)

Если мы хотим составить цепочки функций — func1.func2.func3 или (func1(func2(func3())), то каждая из них может брать лишь по одному параметру. Например, если func2 берёт два параметра — func2(param1, param2), то мы не сможем включить её в цепочку!

Но ведь на практике многие функции берут по несколько параметров. Так как же нам их комбинировать? Решение: каррирование (Currying).

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

Кроме того, каррирование можно использовать при обращении к глобальным переменным, т. е. делать это «чисто».

Посмотрим снова на наше решение:

Пример 2. Работа с кидающими ошибки функциями и выход немедленно после возникновения ошибки

В разделе рассматривается: монада Either

Монада Maybe очень удобна, если у нас есть значения «по умолчанию» для замены Null-ошибок. Но что насчёт функций, которым действительно нужно кидать ошибки? И как узнать, какая функция кинула ошибку, если мы собрали в цепочку несколько кидающих ошибки функций? То есть нам нужен быстрый отказ (fast-failure).

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

Монада Either

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

Применение: в приведённом ниже императивном коде мы вычисляем tax и discount для item’ов и отображаем showTotalPrice.

Обратите внимание, что функция tax кинет ошибку, если значение цены будет нечисловое. По той же причине кинет ошибку и функция discount. Но, кроме того, discount кинет ошибку, если цена item’а меньше 10.

Поэтому в showTotalPrice проводятся проверки на наличие ошибок.

Посмотрим, как можно улучшить showTotalPrice с помощью монады Either и переписать всё в ФП-стиле.

Монада Either предоставляет два конструктора: Either.Left и Either.Right. Их можно считать подклассами Either. Left и Right — это монады! Идея в том, чтобы хранить ошибки/исключения в Left, а полезные значения — в Right. То есть в зависимости от значения создаём экземпляр Either.Left или Either.Right. Сделав так, мы можем применить к этим значениям map, chain и т. д.

Хотя и Left, и Right предоставляют map, chain и пр., конструктор Left только хранит ошибки, а все функции реализует конструктор Right, потому что он хранит фактический результат.

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

Этап 1. Обернём возвращаемые значения в Left и Right.

Примечание: «обернём» означает «создадим экземпляр какого-то класса». Эти функции внутри себя вызывают new, так что нам не придётся это делать.

Этап 2. Обернём исходное значение в Right, потому что оно валидное и мы можем его комбинировать (compose).

const getItemPrice = (item) => Right(item.price);

Этап 3. Создадим две функции: одну для обработки ошибки, вторую для обработки результата. Обернём их в Either.either (из ramda-fantasy.js api).

Either.either берёт три параметра: обработчика результата, обработчика ошибки и монаду Either. Either каррирована, поэтому мы можем передать обработчики сейчас, а Either (третий параметр) — позже.

Как только Either.either получает все три параметра, она передаёт Either либо в обработчик результата, либо в обработчик ошибки, в зависимости от того, чем является Either — Right или Left.

Этап 4. Используем метод chain для комбинирования функций, кидающих ошибки. Передадим их результаты в Either.either (eitherLogOrShow), которая позаботится о том, чтобы ретранслировать их в обработчик результата или ошибки.

Соберём всё вместе:

Пример 3. Присвоение значения потенциальным Null-объектам

***Использована ФП-концепция: аппликативность (Applicative)

Применение: допустим, нужно дать пользователю скидку, если он залогинился и если действует промоакция (т. е. существует скидка).

image

Воспользуемся методом applyDiscount. Он может кидать null-ошибки, если пользователь (слева) или скидка (справа) является null.

Посмотрим, как можно решить это с помощью аппликативности.

Аппликативность (Applicative)

Любой класс, имеющий метод ap и реализующий спецификацию Applicative, называется аппликативным. Такие классы могут использоваться в функциях, которые работают с null-значениями как с левой стороны (пользователь) уравнения, так и с правой (скидка).

Монады Maybe (как и все монады) тоже реализуют спецификацию ap, а значит, они тоже аппликативные, а не просто монады. Поэтому на функциональном уровне мы можем использовать монады Maybe для работы с null. Посмотрим, как заставить работать applyDiscount с помощью монады Maybe, используемой как аппликативная.

Этап 1. Обернём потенциальные null-значения в монады Maybe.

Этап 2. Перепишем функцию и каррируем её, чтобы передавать по одному параметру за раз.

Этап 3. Передадим через map первый аргумент (maybeUser) в applyDiscount.

Иными словами, теперь у нас есть функция, обёрнутая в монаду!

Этап 4. Работаем с maybeApplyDiscountFunc.

На этом этапе maybeApplyDiscountFunc может быть:

1) функцией, обёрнутой в Maybe, — если пользователь существует;
2) Nothing (подклассом Maybe) — если пользователь не существует.

Если пользователь не существует, то возвращается Nothing, а все последующие действия с ним полностью игнорируются. Так что если мы передадим второй аргумент, то ничего не произойдёт. Также не будет null-ошибок.

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

Ой-ёй: map не знает, как выполнять функцию (maybeApplyDiscountFunc), когда она сама внутри Maybe!

Поэтому нам нужен другой интерфейс для работы по такому сценарию. И этот интерфейс — ap!

Этап 5. Освежим информацию о функции ap. Метод ap берёт другую монаду Maybe и передаёт/применяет к ней хранимую им в данный момент функцию.

Можем просто применить (ap) maybeApplyDiscountFunc к maybeDiscount вместо использования map, как показано ниже. И это будет прекрасно работать!

Для сведения: очевидно, в спецификации Fantasy Land внесли изменение. В старой версии нужно было писать: Just(f).ap(Just(x)), где f — это функция, х — значение. В новой версии нужно писать Just(x).ap(Just(f)). Но реализации по большей части пока не изменились. Спасибо keithalexander.

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

Функция curryN

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

Но что если вместо добавления всего двух чисел функция add могла суммировать все числа, передаваемые в неё в качестве аргументов?

const add = (. args) => R.sum(args); //Суммирует все числа в аргументах

Мы всё ещё можем каррировать её, ограничив количество аргументов с помощью curryN:

Использование curryN для ожидания количества вызовов функции

Допустим, нам нужна функция, которая пишет в лог только тогда, когда мы вызвали её три раза (один и два вызова игнорируются). Например:

Можем эмулировать такое поведение с помощью curryN.

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

Пример 4. Сбор и отображение ошибок

В разделе рассматривается: валидация (известна как функтор Validation, аппликативность Validation, монада Validation).

Валидациями обычно называют аппликативность Validation (Validation Applicative), потому что она чаще всего применяется для валидации с использованием функции ap (apply).

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

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

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

image

Для этого воспользуемся функтором Validation. Посмотрим, как это можно реализовать с помощью аппликативности Validation.

Возьмём из folktalejs библиотеку data.validation, в ramda-fantasy она ещё не реализована. По аналогии с монадой Either у нас есть два конструктора: Success и Failure. Это подклассы, каждый из которых реализует спецификации Either.

Этап 1. Для использования валидации нам нужно обернуть правильные значения и ошибки в конструкторы Success и Failure (т. е. создать экземпляры этих классов).

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

Этап 2. Создадим пустую функцию (dummy function) для хранения успешного статуса проверки.

const returnSuccess = () => 'success';//просто возвращает success

Этап 3. Используем curryN для многократного применения ap

С ap есть проблема: левая часть должна быть функтором (или монадой), содержащим функцию.

Допустим, нам нужно многократно применить ap. Это будет работать, только если monad1 содержит функцию. И результат monad1.ap(monad2), т. е. resultingMonad, тоже должен быть монадой с функцией, чтобы можно было применить ap к monad3.

В общем, чтобы дважды применить ap, нам нужны две монады, содержащие функции.

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

Допустим, мы сделали что-то такое.

Предыдущий код не станет работать, потому что результатом Success(returnSuccess).ap(isUsernameValid(username)) будет значение. И мы не сможем применить ap ко второй и третьей функциям.

Можно использовать curryN, чтобы возвращать функцию до тех пор, пока она не будет вызвана N раз.

Теперь каррированная success возвращает функцию три раза.

Луис Атенсио - Функциональное программирование на JavaScript. Как улучшить код JavaScript-программ обложка книги

24 %

Аннотация к книге "Функциональное программирование на JavaScript. Как улучшить код JavaScript-программ"

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

В сложных веб-приложениях низкоуровневые детали JavaScript-кода могут затруднить анализ программы и повлиять на работоспособность системы в целом. Функциональное программирование (ФП) как стиль написания кода способствует слабо связанным отношениям между отдельными компонентами приложений и позволяет составить общее представление о проекте, упростить его разработку, общение с заказчиками и сопровождение.
В этой книге поясняются методики усовершенствования веб-приложений, влияющие в том числе на их расширяемость, модульность, повторное использование и тестируемость, а также производительность. В удобной для чтения форме на конкретных примерах и доходчивых пояснениях демонстрируется, как пользоваться методиками ФП на практике. Начинающие осваивать ФП по достоинству оценят немало удачных примеров сравнения ФП с императивным и объектно-ориентированным программированием, что позволяет лучше понять особенности функционального проектирования. Прочитав эту книгу, читатель научится осмысливать свои проекты функционально, а возможно, дорастет и до понимания монад!
Основные темы книги:
- Применение ценных методик ФП на практике и там, где это наиболее целесообразно
- Отделение логики системы от подробностей ее реализации
- Обработка ошибок, тестирование и отладка прикладного кода в стиле ФП
- Демонстрация и обсуждение всех примеров кода на JavaScript, написанных по стандарту ES6 (ES 2015)
Книга адресована разработчикам, твердо усвоившим основы программирования на JavaScript и обладающим достаточным опытом проектирования веб-приложений.
Об авторе:
Луис Атенсио - инженер-разработчик и архитектор приложений масштаба предприятия на языках Java, PHP и JavaScript.

Функциональное программирование на JavaScript: как улучшить код JavaScript-программ

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

  • Применение ценных методик ФП на практике и там, где это наиболее целесообразно.
  • Отделение логики системы от подробностей ее реализации.
  • Обработка ошибок, тестирование и отладка прикладного кода в стиле ФП.
  • Демонстрация и обсуждение всех примеров кода на JavaScript, написанных по стандарту ES6 (ES 2015).

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

Об авторе:
Луис Атенсио — инженер-разработчик и архитектор приложений масштаба предприятия на языках Java, PHP и JavaScript.

Читайте также: