The Observable disguised as an IO Monad

Luis Atencio
7 min readAug 25, 2016

So, before we talk about IO and Observable monads, I’ll briefly explain what monads are. A monad is nothing more than an algebraic data type that follows a certain interface or protocol (this is by no means a complete explanation of monads, so I’ll refer you to this page for more info on this topic):

  1. A unit function used to place (lift) a value into the monad container. You could also find it as from() or return().
  2. A mapping function that lifts regular functions that work over these values, returning back a new instance of (generally) the same type containing the newly computed value.
  3. A flatMap() also known as (bind()) that lifts a monad-returning function to work over the value in the container and flatten its result into a single structure.

A simple pseudo-interface can be written as follows (to keep it simple I’m omitting any static type information):

interface Monad {
of(a): Monad<a>
map(a -> b): Monad<b>
flatMap(a -> M<b>): Monad<b>
}

Concrete monad classes are a super set of this interface, and all typically implement it in the same way, for the most part. Monads must abide by certain mathematical laws derived from these operations. This is why you can easily switch between different types of monads and all should pretty much have the same expected behavior.

However, a lot of what you do with them in practice comes from the additional logic (the extended “behavior”) that characterizes each monad uniquely depending on the problem it tries to solve. For instance, the Maybe monad in Folktale.js implements an additional unit function called fromNullable() that extends the functionality of the abstract unit function of() with additional null-check logic. The Try monad, used to wrap computations that may succeed or throw an exception, includes additional methods such as isSuccess() or isFailure(). These are all implemented in addition to base methods outlined above.

Whether you see monads as mappings (or morphisms) between categories or as a design pattern to control state and data flow, the bottom line is that pure functional programmers use monads to express things we can’t express with pure functions such as: state mutations, I/O, error handling, reading input from the user, and many others. In this post, we’ll look at two very powerful monads: IO and Observable, both different, yet alike…

The two monads we’ll discuss in this post each specialize in a certain type of impure operation. The IO monad, allows you to thread together a sequence of side effects, so that they can be run in a sequential, predictable, manner. The Observable monad, has built-in logic that allows you to thread together a sequence of asynchronous computations as if you we’re mapping regular synchronous functions — again this is the extended behavior. With that said, let’s begin!

The IO Monad

IO can be used to create a referentially transparent-ish (if that’s even a term) program specification made up of functions that may produce side effects like reading or writing to a DOM element. Here’s an example of a program that rotates a DIV on an HTML page. The rotate() function I’ll use takes 3 arguments, the browser Document object, the selector string, and the number of degrees to rotate:

const rotate = function(document, id) {
return function(deg) {
let elem = document.querySelector(`${id}`);
elem.style[‘transform’] = `rotate(${deg}deg)`;
};
};

As you can see, I’m using a deferred (lazy) function so that I can partially evaluate the initial conditions using Lodash.js and allow it easily compose:

const rotateDom = _.partial(rotate, document);

This is common when working with IO functionally — remember you don’t want the functions to run eagerly, you want IO to execute these effects for you. IO is a very simple monad that implements a slightly modified version of our abstract interface with the difference that instead of wrapping a value a, it wraps a side effect function () -> a. I’ll show the pertinent pieces related to this post (adapted and updated up from the monet.js project):

class IO {   constructor(effect) {
if (!_.isFunction(effect)) {
throw ‘IO Usage: function required’;
}
this.effect = effect;
}
// unit function
static of(a) {
return new IO( () => a );
}
// map function
map(fn) {
let self = this;
return new IO(() => fn(self.effect()));
}
// monadic bind
flatmap(fn) {
let self = this
return new IO(() => fn(self.effect()).run());
}

run() {
return this.effect();
}
}

With IO I can map operations over effects and flatMap() to bring in other IO sequences of operations. There’s really no other behavior aside from the standard interface (except for run(), which I’ll discuss in a bit). Here’s how I can thread together a sequence of operations both pure and impure and cause the DIV to rotate:

IO.of(rotateDom('#name')).map(doNIntervals(12, 100)).run();

The rotateDom() function that causes the “effect” is mapped over a function that shows the animation rotating 12 times (30° at a time) every 1 second for a full 360° turn:

So, with IO I get a lazy program description of a side effect with the promise of executing immediately or in the future. In order to appreciate the animation effect, I need to use a series of functions that execute asynchronous to the runtime of the code. Behind the scenes, this is what doNIntervals() does.

function doNIntervals(n, interval) { 
return function times(fn) {
setTimeout(() => {
fn(n * -30);
if( — n >= 0) {
times(fn);
}
}, interval);
};
}

As you know, in functional programming we use recursion as the means of iteration (because you can’t really map a for-loop into a monad, can you). In order to carry out the animation, I used a function that can perform a series of iterations that spawn functions that execute in interval milliseconds. I needed to do this manually, because IO doesn’t support this out-of-the-box. But there’s a monad that does: the Observable.

The Observable

Until the time comes when observables become a native part of JavaScript (hopefully in ES7), you can work with observables using RxJS. This monad not only allows you to perform regular IO as I did previously, but also asynchronous IO, as it makes the notion of time a first-class citizen — this is that “extra behavior” I referred to earlier. It’s a much more powerful and feature-rich monad than IO that implements the basic abstract interface as well as a ton of additional functionality for manipulating sequences of events over time.

Porting the same code above to rotate another panel with Observable is effortless:

Rx.Observable.interval(100)
.map(val => val * 30 + 30)
.take(12)
.subscribe(rotateDom('#name2'));

As you can see, my little convoluted recursive function boiled down to a sequence of simple operators that bend time as I wish. In this case, the unit function (also called static factory operator) is interval(), which lifts a sequence of integers separated by 100 milliseconds. I’m also using a simple map() with a pure function, and then take(), which you can think as a filter() underneath. RxJS also has a flatMap() operation (called mergeMap() in RxJS 5).

Comparing IO and Observable

Finally, here’s the catch. Both IO and Observable have a striking resemblance, because it’s no coincidence that monads that allow you to perform side effects are also lazily evaluated (I’m referring particularly to cold observables in this case). They actually need to be to keep these sequences of operations “pure.” Now it’s time to go back to IO’s run() method (also known as performUnsafeIO()). There are 2 main reasons for this:

  1. Both monads are lazy in that the Observable won’t begin executing until subscribe() is called, and similarly IO won’t start until run() is called.
  2. IO’s run() is what causes the chain of side effects to propagate and flush out any pending IO operations. Likewise, subscribe() receives any events propagated from the streams that it’s made up of and performs any necessary IO operations, rotating the DIV in this case. In a pure FRP observable chain, all of the side effects should be propagated downstream to the subscribers.

At a high-level, both monads are just two peas in a pod. They have the same basic structure plus a method to kick-off their functionality, which leads me to think that Observable is just a specialized form of IO — the AsyncIO monad ?— with a whole arsenal of timer and async operators built-in to it.

In conclusion: The Observable is really an IO monad in disguise!

Edit: I was able to verity this actually holds after reading Efficient and Compositional Higher-Order Streams. Now, I’m no Haskeller, but this definition of an Observable looks perfect:

newtype Observable a = S (IO a) deriving (Functor , Applicative, Monad)

If you don’t know me, my name is Luis Atencio. Writing about this is my passion, which you can read more about in Functional Programming in JavaScript and RxJS in Action.

Hope this post was helpful!

--

--

Luis Atencio

Author of Functional Programming in JavaScript, RxJS in Action, and The Joy of JavaScript