Функциональное программирование с примерами на JavaScript. Часть вторая. Аппликативные функторы, curryN и валидации

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

Пример 3.  Задание значений объектам, которые могут равняться Null

Используемые концепты ФП: Аппликативные функторы.

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

скидка

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

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

Аппликативный функтор

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

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

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

Шаг 1: Обернем потенциальные null-объекты в Maybe-монады.

Шаг 2: Перепишем функцию так, чтобы она могла принимать один параметр за раз (каррируем её).

Шаг 3: Передадим первый агрумент (maybeUser) в метод applyDiscount, используя map.

Шаг 4: Используем maybeApplyDiscountFunc.

Значение maybeApplyDiscountFunc может быть:

  1. функцией, обернутой в Maybe, если пользователь существует.
  2. Nothing, если пользователь равен null.

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

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

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

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

Шаг 5: Используем функцию ap. Этот метод принимает монаду Maybe и выполняет функцию, хранящуюся внутри.

Применим метод ap:

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

Множественное каррирование

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

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

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

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

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

Мы можем написать эту функцию в функциональном стиле, используя curryN:

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

Освещенные темы: Валидации (Валидационный функтор, Валидационный аппликативный функтор, Валидационная монада)

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

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

Сценарий использования: У нас есть форма регистрации, в которой валидируются имя пользователя, пароль и e-mail с помощью трех функций: isUsernameValid, isPwdLengthCorrect и isEmailValid. Мы должны показать одну, две или три ошибки в зависимости от введенных данных.

форма регистрации

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

Мы будем использовать библиотеку data.validation из folktalejs, поскольку в ramda-fantasy еще не реализованы валидации.

У валидационного функтора есть два конструктора: Success и Failure, по аналогии с монадой Either.

Шаг 1: Чтобы использовать валидации, все, что нам нужно сделать — обернуть валидные значения и ошибки в Success и Failure.

Проделайте это для всех полей формы.

Шаг 2: Создайте функцию-заглушку.

Шаг 3: Используйте curryN, чтобы повторно применить ap.

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

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

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

Давайте предположим, что мы сделали что-то вроде:

Код выше не сработает, потому что Success(returnSuccess).ap(isUsernameValid(username)) вернет значение, и мы не сможем вызвать от него метод ap.

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

В результате мы получаем такой код:

Перевод статьи «Functional Programming In JS   With Practical Examples (Part 2)»