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 | // original length function without input data check |
Even more interesting in this case, what happens if Maybe instance contains undefined
?
1 | new Maybe() |
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 | function Maybe(x) { |
Even better, let us avoid wrapping a value in Maybe object, if the value has already been wrapped once
1 | function Maybe(x) { |
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 | q('foo') |
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.