The True Use of Monads in FP
Part 2: The Motivating Problem in JS
The Motivating Problem
Your manager asks you to write a function countCharsOfFriendsPosts
that, given a user ID, counts the number of characters in posts made by friends of the user. Your manager tells you the user database will be represented as an array in your local memory as displayed below:
Being the expert functional programmer that you are, you quickly recognize countCharsOfFriendsPosts
as being the composition of the 3 following smaller functions:
getUserFriends
Given a user ID, query a database for the user’s friends.getUsersPosts
Given an array of users, query the database for posts made by any of the users.countChars
Given an array of posts, count the number of characters in the posts.
You then implement the functions as follows:
And finally you have countCharsOfFriendsPosts
:
“Wow! What simple elegance,” you think to yourself, “what an amazing specimen of functional programming.” You expect your manager to be thrilled with your extreme powers of composition, however you find him less enthused than expected. He informs you that he made a mistake in the specs and the database that stores a user’s friends is on a remote server and thus needs to be requested asynchronously.
Your current code can be viewed and tested here: Ramda-Repl-1
A (Not-So) Quick Detour On The True Use Of Functors In FP
Your manager quickly convinces you that this was the only mistake he made in the specs, and that the user’s posts are indeed represented in local memory. Undeterred, you rewrite getUserFriends
to return a Promise of a List of UserIDs.
However you quickly see a road block ahead; this completely messes up your elegant composition! getUserFriendsAsync
returns a Promise (List UserID) while getUserPosts
takes a List UserID. The types don’t line up!
Sub-detour 1: Promises And Then
Before we solve this problem I want to clear up some confusion around the JS Promise’s then
method. The function passed to the then
method has two acceptable signatures:
To avoid possible confusion, instead of using the then
method, I will refer to the following two functions:
Sub-detour 2: The Quick and Dirty Definition of A Functor
In a future post I’ll elaborate on the full definition, but for now this is perfect:
A functor F is a function on functions such that for all pairs of functions h and g:
1. If h can be composed after g, then F(h) can be composed after F(g);
2. F(h) ∘ F(g) = F(h ∘ g).
In code: F(c(h, g)) == c(F(h), F(g))
In other words, it doesn’t matter if we first compose h ∘ g and then apply F, or apply F to each and then compose. That’s it. That’s the whole thing.
Back to our story…
Image of Composed Functions
After a brief moment you realize there is only one thing to do here: somehow transform the composition of getUsersPosts
and countChars
and compose that with getUserFriendsAsync
to get a new countCharsOfFriendsPostsAsync
as follows:
Here’s a link: Ramda-Repl-2
Although not quite as elegant as your original simple composition, you are quite satisfied with this solution. You are about to get up and pass this off to your manager when you instead see him running up to you. Huffing and puffing, he explains that now that your function is asynchronous, Upper Management insists on providing a Daily Default Post (DDP) to count, should anything go wrong while trying to get the user’s friends’ posts. The DDP will be available in local memory, and your program should count the characters of the DDP if, unfortunately, something goes wrong with getting the user’s friends’ posts.
You quickly implement a catch function with the hope of inserting it where required to provide the DDP should an error have occurred in either getting the user’s friends or the friends’ posts.
You study your current configuration —
pmap(countChars ∘ getUsersPosts) ∘ getUserFriendsAsync
— and realize that you would need to put a “catch
” after getUsersPosts
, but before countChars
. You start sweating when you realize that once again the types don’t line up! pcatch takes and returns a promise of an array of Strings, but the return type of getUsersPosts
and the input type of countChars
is just an array of Strings!
You loudly lament how if, instead of
pmap(countChars ∘ getUsersPosts),
you had
pmap(countChars) ∘ pmap(getUsersPosts),
all your problems would be solved as then you could compose as follows:
The guy sitting next to you, a functional programmer, whispers in your ear “Promise is a functor” (or if he was a mathematician maybe “pmap is a functor”). You instantly light up. For pmap to be a functor, that would mean that for any functions g and f:
pmap(f ∘ g) = pmap(f) ∘ pmap(g)
And indeed, you can safely replace pmap(countChars ∘ getUsersPosts) with pmap(countChars) ∘ pmap(getUsersPosts).
Thanks to functors, you are confident in the following code:
In short, functors map functions in such a way that composing after mapping or mapping after composing provide the same result.
View it in here: Ramda-Repl-3
Up Next: The Motivating Problem Cont.
Asher Klein is the Founder & CEO of Descript Software. He holds a degree in Pure & Applied Mathematics from Concordia University.