Функциональное программирование с примерами на JavaScript. Часть вторая. Аппликативные функторы, curryN и валидации
14К открытий14К показов
В прошлой части мы рассмотрели основные инструменты функционального программирования с примерами на 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 может быть:
- функцией, обернутой в Maybe, если пользователь существует.
- 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 раз.
В результате мы получаем такой код:
14К открытий14К показов