Combine promises with Maybe functors

Functor Promise together with Maybe applicative.

This post was inspired by the cat pictures in Functors and Applicatives.

I showed that promises are functors in the previous blog post. In this post I will show how to combine another functor Maybe with promises. I will use Q promise library and in my examples.

A functor is a wrapper for an object that can apply a function to the value and return new functor of the same type. Promises are functors since they can apply a function via .then method and then continue with new promise. I will first define another useful functor called Maybe that can stop execution if the value is undefined.

Maybe functor

Let us start with a simple function that returns a length of a string (or an array).

function length(x) {
    return x.length;
}

We can use this function with strings, but if we pass an undefined or null function, it dies a horrible death

console.log(length('foo')); // 3
console.log(length()); // TypeError: Cannot read property 'length' of undefined

How do we guard our application logic against runtime errors like these? Usually by modifying the 'length' function itself

function length(x) {
  if (typeof x !== 'undefined')
    return x.length;
}
console.log(length('foo')); // prints 3
console.log(length()); // prints undefined

This solution has two downsides

  • We had to modify the function's code. We could wrap the function dynamically, but still this is less than ideal.
  • We did not stop the execution chain. We have stopped the exception, but the console.log still executed and printed 'undefined'.

Instead of applying the length function directly to the value, let us wrap the value in a functor instance. The functor object will have a method to run the function on the contained value and will always return another (or the same) functor instance. The functor Maybe will apply the function only if there is a value to be applied.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// original length function without input data check
function length(x) {
return x.length;
}
// Maybe keeps given value
function Maybe(x) {
this.value = x;
}
// returns transformed value or itself
Maybe.prototype.map = function (fn) {
return typeof this.value === 'undefined' ?
this :
new Maybe(fn(this.value));
}
new Maybe('foo')
.map(length)
.map(console.log);
// prints 3

Even more interesting in this case, what happens if Maybe instance contains undefined?

1
2
3
4
new Maybe()
.map(length)
.map(console.log);
// nothing!

Nothing happens. Function length is not called, because the value is undefined. Then the method console.log is not called, because the value is undefined. We safely skipped all the steps.

Maybe functor without new keyword

I described how to transform a constructor function to make new optional. Now we can easily use Maybe as callback function for example, or start the chain without new keyword.

1
2
3
4
5
6
7
8
9
function Maybe(x) {
if (this instanceof Maybe)
this.value = x;
else
return new Maybe(x);
}
Maybe('foo')
.map(length)
.map(console.log);

Even better, let us avoid wrapping a value in Maybe object, if the value has already been wrapped once

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Maybe(x) {
if (x instanceof Maybe) {
return x;
}

if (this instanceof Maybe)
this.value = x;
else
return new Maybe(x);
}
console.log(Maybe('foo'));
console.log(Maybe(Maybe('foo')));
// prints
{ value: 'foo' }
{ value: 'foo' }

Promises are functors

Promises are functors. Example:

var q = require('q');
function double(x) { return x + x; }
q('foo').then(double).then(console.log);
// prints foofoo

Here is typical situation where we start with a promise object and then try to run length on the result

q('foo')
    .then(length)
    .then(console.log);
// prints 3

Let us try starting the chain with undefined value.

q()
    .then(length)
    .then(console.log);
// nothing happens

Nothing happens because there is an exception that is silently swallowed by the Q engine. We need to close promise chain using .done method, see why promises need to be done.

q()
    .then(length)
    .then(console.log)
    .done();
// TypeError: Cannot read property 'length' of undefined

We see the error, let us try guard against it using the Maybe functor we used above.

Maybe with promises problem

Let us introduce the Maybe function into the promise started by the Q. We would like to avoid calling functions if the value passed through the promise chain is undefined.

If we try the chain directly, it does not work

q()
  .then(Maybe)
  .then(length)
  .then(console.log)
  .done();
// prints 'undefined'

This is not what we needed. If we look at the run-time details, here is what is happening. I will add to each line the owner of the .then call and the type of the result passed around.

q() 
  .then(Maybe)          // Q.then calls Maybe constructor
  .then(length)         // Q.then calls `length` with Maybe instance wrapping undefined. 
                        // `length` returns { Maybe }.length which is undefined
  .then(console.log)    // Q.then calls console.log(undefined)
  .done();
// prints 'undefined'

Notice the above code is not using Maybe as a functor. Instead of wrapping a value, we need Maybe to wrap a function to be executed. We need something like this:

q()
  .then(Maybe(length)) // wraps length, waits for value
  .then(Maybe(console.log)) // wraps console.log, waits for value
  .done();

Wrapping functions

If we wrap a function to be applied to wrapped value, it becomes an applicative. We can apply the applicative to a value using .ap method

Maybe.prototype.ap = function ap(x) {
  if (typeof this.value === 'function')
    return Maybe(x).map(this.value); // 1
  return Maybe();
};
Maybe(console.log).ap('foo');
// prints "foo"

Thus the applicative is wrapped function that operates on the wrapped value. This is why we were checking if the value is already wrapped inside the Maybe constructor - so we can safely call wrapper on every value before calling return Maybe(x).map(this.value); // 1.

If we want to use the applicative as a callback, we need to bind the .ap method to the Maybe instance

var MaybeLog = Maybe(console.log);
var cb = MaybeLog.ap.bind(MaybeLog);
setTimeout(function () {
  cb('foo');
}, 0);
// prints "foo" after timeout

Applicative as promise callback

Now we can apply conditional functions to values passed by the promise

var q = require('q');
var MaybeLength = Maybe(length);
var MaybeLog = Maybe(console.log);
q('foo')
  .then(MaybeLength.ap.bind(MaybeLength))
  .then(MaybeLog.ap.bind(MaybeLog))
  .done();
// prints 3 asynchronously
q()
  .then(MaybeLength.ap.bind(MaybeLength))
  .then(MaybeLog.ap.bind(MaybeLog))
  .done();
// does nothing

I do not like remembering to bind the .ap to the Maybe instance, so let us change .ap method to access both the wrapped function and the passed value using lexical scope instead of this context. We will do this by moving .ap from prototype directly inside the constructor function. This will use more memory (every instance gets its own instance of ap function), but will not require the binding.

function Maybe(x) {
  if (x instanceof Maybe) {
    return x;
  }
  if (this instanceof Maybe) {
    this.value = x;
    this.ap = function ap(arg) {
      // access function x via lexical scope
      if (typeof x === 'function')
        return Maybe(arg).map(x);
    };
  } else
    return new Maybe(x);
}

Now we can quickly wrap functions passed as steps to the promise

1
2
3
4
5
6
7
8
9
10
q('foo')
.then(Maybe(length).ap)
.then(Maybe(console.log).ap)
.done();
// prints 3 asynchronously
q()
.then(Maybe(length).ap)
.then(Maybe(console.log).ap)
.done();
// prints nothing

Conclusion

Wrapping raw values inside functors and applicatives helped me refine and simplify the code in my programs. This is an unfinished process, and I am still looking for a more practical approach.