Sweeten your Promise with Monad in ReasonML

digitake
LambdaSide
Published in
4 min readMay 1, 2018

--

Promise is one of the most I found useful construct in the web programming. Because of its async nature. Promise provides a nice way to put a code into synchronous chunks and each chunk is called at an asynchronous point of time.

Suppose we want to get data from two API endpoints in an orderly fashion. We should not perform a blocking call on each API fetch because it against the nature of JavaScript, Let alone that it does not even consider acceptable from a user perspective. All UI components will be unusable, and it will freeze the browser. One must have a very good reason to try to do it.

An asynchronous solution can be done with a Promise object. It will keep you a promise, to come back with a value after finish the execution. The code goes like this:

Using basic promises to make async http request orderly.

The promise object will be created by fetch command, and then the response object will be sent and appear in the next .then as a function argument. The code above is little complicated as it hits a URL first and then grab a JSON content of the URL. But I want to keep it this way to close to a real-life use case.

Illustrate promise chaining

The image above shows how a promise object sends their results to next `.then` . The blocks represent execution context of each function. The difference to a normal function call is that they are async at the point of call but it will wait until a function resolve (or reject) is called before executing next block.

The result of Promise will be always a Promise. Even resolve is not called, Promise will take a return value and pass it to the next execution. To this point, one might feel familiar with the notion of cannot escape the box. YES, Promise is a monad. To keep this article short, I will show that it is a monad in another article, or you can prove it yourself that it satisfies two properties, namely, return and >>=.

Yes, Promise is a monad.

For now, let us get back to the title of this article. ReasonML provides a binding for Promise in Js.Promise object. We can achieve the same code above in ReasonML in this way:

Using Promise in ReasonML

The code above does the same thing as the JavaScript version. Every time we want to start a new block of execution, we have to put into then_ keyword, which is wasting time and make the code harder to read. We can put monadic expression at work to sweeten our code a bit. I defined the PromiseMonad as shown below:

PromiseMonad module definition

Actually, to make it become a Monad we only need return and (>>=) operator. defer is there just to make my life simpler to create the promise object that gonna resolve in the first place. Let us take a close look at their type

return: a' => Js.Promise.t('a)

That is a simple lift, it will take any type and wrap it in the Promise type. Now let’s take a look that our workhorse, the bind function

(>>=): (Js.Promise.t('a),'a=>Js.Promise.t('b))=> Js.Promise.t('b)

Trivially, it takes

  1. Promise of any type 'a
  2. a function that takes the same type 'a that will produce a Promise of another type 'b.

This bind functions will produce a Promise of the other type'b for you!!!

Using the Monad we defined will be easy if the code doesn’t require a function call that return the value from another normal function.

Using the PromiseMonad

It seems like we need another function that helps us kick start the lazy Monad. This function should take any function type and return us Promise of any type we want. That is why defer come to rescue us. The function type goes like this:

defer: (. 'a) => unit => 'b => Js.Promise.t('a)

which basically means, it takes a function and it will return a Promise object of any type that the function is return. The fancy (. 'a) is a ReasonML syntax that it is to create JavaScript object, not a record type. That is the requirement of make function. Maybe, it will become more clear when we see how to use it.

Usage

defer function is generic enough to take in any function and wrap it with a nice, warm Promise.

Usage with existing module bs-fetch.

Lastly, we are not forget to visit the code at the top of this article and see how to rewrite that using new monadic style. As a result, we can use promise with clearer syntax and the code is now sweeter~

Note: I created repo for this little tool and also npm package for humanity.

--

--

digitake
LambdaSide

Lambda school of thought, minimalist, mathematical minded. Love AI, Functional, Logic.