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

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

Disclaimer: Please note that I am not an authority on functional programming by any means. I intend this little series solely as “introduction to concepts”. This guide is quite unconventional in the order of the content. I choose to focus on practical examples, leaving essential theory such as typeclasses and function purity until late in the game. If you thirst for more technical resources, I highly recommend https://github.com/MostlyAdequate/mostly-adequate-guide, or you can also check out this free ebook on Haskell: https://learnyouahaskell.com/.

Помните, что функторы - это просто структуры данных, которые можно отображать. Другими словами, структура данных (или «контейнер») предоставляет интерфейс (мы называем его fmap) для условного изменения значения, которое мы храним, и снова обертывает его для нас. Фактически, простой массив - это функтор: это структура данных, которую мы можем «сопоставить» (используя Array.map).

Хорошо, давайте продолжим наше приключение!

Функтор гимнастики

Итак, у нас остался этот код из части 2:

Давайте проведем рефакторинг:

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

Более интригующим аспектом здесь может быть функция functor, которая возвращает Object.assign, которая объединяет переданные вами объекты в один. Вот и все. В этом случае первый параметр, который мы передаем, является новой копией maybe, а второй параметр - литералом объекта, чтобы поместить значение, которое мы хотим сохранить внутри контейнера.

Object.assign объединяет эти два объекта в один объект: функтор! Он работает так же, как и раньше, но теперь fmap функтора существует только в прототипе. Все maybe контейнеры объектов будут указывать на одну и ту же fmap область памяти.

И последнее, но не менее важное: мы должны адаптировать способ создания функтора Maybe. Теперь у нас есть:

functor(maybe, “Doc Emmett Brown”);

вместо того:

Maybe(“Doc Emmet Brown”);

Заостренные функторы подкрадываются к нам

Этот синтаксис для создания функтора может показаться шагом назад. Так почему бы просто не добавить его к самому объекту Maybe? Давайте maybe дадим новый метод и назовем его… of. Взглянем:

Именно так мы добавили of метод к нашему объекту Maybe. Не позволяйте тому факту, что of вызывает maybe внутри него, противоречит вам: maybe.of просто отвечает за создание для нас нового функтора Maybe.

Мы можем вызвать maybe.of(“whatever we want”) , чтобы поместить значение внутри контейнера Maybe, и мы можем с радостью начать «fmapping» над ним, то есть: условно изменить значение внутри него!

maybe.of(“cool”).fmap(val => val + “!”);
// «круто!»

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

В конце концов, мы создали тип возможно с методом of, методом fmap и значением. В конце концов, мы перестанем создавать собственные реализации функторов и будем использовать библиотеки (такие как Folktale), но важно понимать, что происходит за кулисами, по крайней мере, на высоком уровне.

Мои программы не безошибочны!

Контейнер maybe хорош: он обрабатывает пустые данные и проверяет NULL за нас; но этого недостаточно. Совсем мало! Мы хотим учтиво выявлять ошибки и ошибки. Мы хотим получать обратную связь от нашей композиции, когда что-то идет не так.

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

Сейчас мы создаем пользователя с maybe.of(“Doc Emmett Brown”).

Давайте представим неверные данные. Предположим, какая-то неуклюжая душа или злой дьявол переходит в строку «Док Браун» в firstInitial (без имени «Эммет»). Очевидно, это пройдет maybe нулевую проверку, и в конце firstInitial вернет “B”. Хотя это не приводит к сбою нашей программы с ошибкой, это не то, что мы хотели! В этом случае мы хотим замкнуть композицию и вернуть другую строку вместе, например: “Not a valid string”.

Теперь у нас есть два варианта вывода, с которыми нужно бороться: результат или сообщение об ошибке (игнорируя на мгновение возможное значение null).

Чтобы учесть это разделение в дороге, мы создадим два новых функтора: right предоставит нам правильный результат, а left будет отвечать за то, чтобы вежливо сообщить нам плохие новости о том, что что-то не так ... правильно.

На самом деле это просто продолжение того, что мы узнали о функторах. Давайте поговорим об этом:

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

left container вообще ничего не делает! Его метод fmap игнорирует переданную функцию и просто возвращает свой собственный контекст (this); то есть сам контейнер без изменений. Все, что находится в left контейнере, остается там без каких-либо изменений, несмотря ни на что. Это потому, что это будет наше сообщение об ошибке. Предположительно, мы не хотим ничего делать с нашим сообщением об ошибке, мы просто хотим знать, что оно говорит в конце.

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

Давайте рассмотрим код полностью, включая исходный maybe:

Ну вот и все. Обратите внимание на то, что наша map функция действительно очень мощная: ей все равно, какой функтор вы ей передаете, она просто использует соответствующий fmap и передает новый контейнер вниз по композиции.

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

Здесь следует отметить важное изменение: getWords отличается тем, что теперь возвращает контейнер. Он проходит по right или left контейнеру внутри maybe контейнера. Вот почему внутри нашей firstInitial композиции мы должны быть осторожны при отображении этого нового контейнера.

R.pipe(getWords, map(getFirstName), map(getFirstLetter))

Для ясности: после того, как мы «открыли» контейнер maybe и запустили getWords, мы возвращаем либо контейнер right, либо left (а не просто значение), поэтому нам нужно map перед применением следующей функции (s) в составе.

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

Наконец, что это за мерзость, когда мы хотим извлечь выгоду из контейнеров? Нам нужно .val дважды (а иногда нам это нужно только один раз). Я не сказал вам, но получение значения и регистрация его волей-неволей - кощунство в функциональном программировании. Это «побочный эффект». Такая банальная вещь, как console.log, является побочным эффектом: она меняет состояние мира вне нашей функции. Это вне нашего контроля, и это опасно!

В части 4 мы познакомим вас с функтором IO, который с особой тщательностью предотвращает такие побочные эффекты. Вскоре после этого нам понадобятся монады, чтобы избавить нас от боли, связанной с вложенными контейнерами, с которыми мы уже сталкиваемся.
Следите за обновлениями!

Не стесняйтесь протестировать или разветвить код нашего последнего примера на CodePen.

Вы также можете подписаться на меня в твиттере :)
Спасибо за внимание!