Часть 2: Проблема мотивации в JS

Проблема мотивации

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

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

  1. getUserFriends
    Имея идентификатор пользователя, запросите базу данных друзей пользователя.
  2. getUsersPosts
    По заданному массиву пользователей запросите в базе данных сообщения, сделанные кем-либо из пользователей.
  3. countChars
    Учитывая массив сообщений, подсчитайте количество символов в сообщениях.

Затем вы реализуете функции следующим образом:

И, наконец, у вас есть countCharsOfFriendsPosts:

"Ух ты! Какая простая элегантность, — думаете вы про себя, — какой удивительный образец функционального программирования. Вы ожидаете, что ваш менеджер будет в восторге от ваших невероятных способностей к сочинению, однако находите его энтузиазма меньше, чем ожидалось. Он сообщает вам, что допустил ошибку в спецификациях, а база данных, в которой хранятся друзья пользователя, находится на удаленном сервере, и поэтому ее необходимо запрашивать асинхронно.

Ваш текущий код можно посмотреть и протестировать здесь: Ramda-Repl-1

Краткий (не совсем) обходной путь к истинному использованию функторов в FP

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

Однако вы быстро видите впереди блокпост; это полностью испортит вашу элегантную композицию! getUserFriendsAsyncвозвращает Promise (List UserID), а getUserPosts принимает List UserID. Типы не совпадают!

Отклонение 1: обещания, а потом

Прежде чем мы решим эту проблему, я хочу прояснить некоторую путаницу вокруг метода JS Promise then. Функция, передаваемая методу then, имеет две допустимые сигнатуры:

Чтобы избежать возможной путаницы, вместо использования метода then я буду ссылаться на следующие две функции:

Sub-tour 2: Быстрое и грязное определение функтора

В будущем посте я подробно расскажу о полном определении, но пока это идеально:

Функтор F — это функция на функциях, такая, что для всех пар функций h и g:

1. Если h можно составить после g, то F(h) можно составить после F(g);

2. F(h) ∘ F(g) = F(h ∘ g).

В коде: F(c(h, g)) == c(F(h), F(g))

Другими словами, не имеет значения, составим ли мы сначала h ∘ g, а затем применим F, или применим F к каждому, а затем составим . Вот и все. Вот и все.

Вернемся к нашей истории…

Изображение составных функций

Через некоторое время вы понимаете, что здесь нужно сделать только одно: каким-то образом преобразовать композицию getUsersPosts и countChars и скомпоновать ее с getUserFriendsAsync, чтобы получить новый countCharsOfFriendsPostsAsync следующим образом:

Вот ссылка: Рамда-Репл-2

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

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

Вы изучаете текущую конфигурацию —

pmap(countChars ∘ getUsersPosts) ∘ getUserFriendsAsync

— и поймите, что вам нужно поставить «catch» после getUsersPosts,, но перед countChars. Начинаешь потеть, когда понимаешь, что опять типы не совпадают! pcatch принимает и возвращает promise массив строк, но тип возвращаемого значения getUsersPosts и тип ввода countChars — это просто массив строк!

Ты громко сетуешь, как если бы вместо

pmap(countChars ∘ getUsersPosts),

ты имел

pmap(countChars) ∘ pmap(getUsersPosts),

все ваши проблемы будут решены, так как тогда вы можете составить следующее:

Парень, сидящий рядом с вами, функциональный программист, шепчет вам на ухо «Promise — это функтор» (или, если он был математиком, возможно, «pmap — это функтор»). Вы мгновенно загораетесь. Чтобы pmap был функтором, это означало бы, что для любых функций g и f:

pmap(f ∘ g) = pmap(f) ∘ pmap(g)

И действительно, вы можете безопасно заменить pmap(countChars ∘ getUsersPosts) на pmap(countChars) ∘ pmap(getUsersPosts).

Благодаря функторам вы уверены в следующем коде:

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

Посмотреть его можно здесь: Рамда-Репл-3

Далее: Проблема мотивации, продолжение

Ашер Кляйн — основатель и генеральный директор Descript Software. Он имеет степень в области чистой и прикладной математики Университета Конкордия.