Functional Programming in JavaScript, Part 3: Introduction to Functors and Monads

Thiery Michel
Thiery MichelSeptember 26, 2018
#functional-programming#js#tutorial

After discovering the Unit and the Monoid, it's time to talk about Functors and Monads. I know, those words can be frightening. Their definitions are frightening, too:

Functor

A morphism from a source category to a target category which maps objects to objects and arrows to arrows, in such a way as to preserve domains and codomains (of the arrows) as well as composition and identities.

Monad

simple monad

How is that supposed to be helpful? Let's forget those awful definitions and start with an example of problem the functor and monad resolve.

Checking Input Properties

Let's say I want to increment a number.

const increment = v => v + 1;
const a = 5;
increment(a); // 6

Simple, right? But then someone calls the increment function with an incorrect argument:

increment('5'); // '51'
// or like this
increment({ v: 5 }); // "[object Object]1"

So to handle this case, I need to add a type check to the function:

const increment = v => {
    if (typeof v !== 'number') {
        return NaN; // NaN fit our purpose here, we received not a number, so we return just that
    }

    return v + 1;
};

increment('5'); // NaN
increment({ v: 5 }); // NaN

Good. Now, let's say that I need another function to double a number. Again, to be safe I need to repeat the same type check:

const double = v => {
    if (typeof v !== 'number') {
        return NaN;
    }

    return v * 2;
};

In fact each time I manipulate a value, I should do some verifications:

  • Is it the right type?
  • Is it here?
  • Was there an error?

And sometimes, the value is not even readily available, it comes in a callback, or not, maybe we will get an error.

Echanging The Function And Value Roles

What if instead of passing the value to the functions, we passed the functions to the value? Or rather to a placeholder for the value.

I mean, a value already knows some important things about the functions that will be called with it:

  • If I am null, there is no need to execute the function.
  • If I am not of the correct type, I should skip all functions passed to me.
  • If I am an error, I should not execute this function, but I should execute this other one instead.
  • If I am asynchronous, I should call this function once I arrive

In short, the value knows best what to do with the function.

This is the basic idea behind a pattern called Functor: a placeholder for a value, so that you pass the function to the value, and not the other way around. We are in functional programming after all: we want to manipulate functions, not values.

Let's write a placeholder for a number value: a NumberBox.

const NumberBox = number => ({
    applyFunction: fn => NumberBox(fn(number)),
    value: number,
});

NumberBox(5).applyFunction(v => v * 2).applyFunction(v => v + 1).value; // 11

For now, this code still suffers from the same problem on non-number arguments. So let's enhance the placeholder to check that it holds a numeric value before applying the function.

const NumberBox = number => ({
    applyFunction: fn => {
        if (typeof number !== 'number') {
            return NumberBox(NaN);
        }
        return NumberBox(fn(number))
    },
    value: number,
});

NumberBox(5).applyFunction(v => v * 2).applyFunction(v => v + 1).value; // 11
NumberBox({ v: 5 }).applyFunction(v => v * 2).applyFunction(v => v + 1).value; // NaN

This applyFunction method looks a lot like another function that we often use, doesn't it?

[1,2,3].map(v => v + 1);

That's right, Array.map() works the same: it takes values from an array instead of a box, but it applies the function all the same.

From now on let's rename applyFunction to map, as we map a function over a value.

const NumberBox = number => ({
    map: fn => {
        if (typeof number !== 'number') {
            return NumberBox(NaN);
        }
        return NumberBox(fn(number))
    },
    value: number,
});

And, just like that, I have created a functor, which is a value placeholder offering a map function to execute functions on it.

So to recapitulate, I have a box that holds a value and can apply a function to it thanks to a map method. This allows adding custom logic to the function application. This way I manipulate a function, not a value. And with this pattern, I can do a lot more.

Using The Functor Pattern For Type Checking

Let's generalize the NumberBox function to handle any type checking, not just numbers. I'll create a TypeBox function, that generates a functor based on a test (or predicate, to use a more exact word):

const TypeBox = (predicate, defaultValue) => {
    const TypePredicate = value => ({
        map: fn => predicate(value)
            ? TypePredicate(fn(value))
            : TypePredicate(defaultValue),
        value,
    });
    return TypePredicate;
};

So now I can recreate the NumberBox by passing a number check as predicate, and NaN as default value:

const NumberBox = TypeBox(value => typeof value === 'number', NaN);
NumberBox(5).map(v => v * 2).map(v => v + 1).value; // 11
NumberBox({ v: 5 }).map(v => v * 2).map(v => v + 1).value; // NaN

I can also create a StringBox, a functor for strings:

const StringBox = TypeBox(value => typeof value === 'string', null);
StringBox('world').map(v => 'Hello ' + v).map(v => '**' + v + '**').value; // '**Hello, world**'
StringBox({ v: 5 }).map(v => 'Hello ' + v).map(v => '**' + v + '**').value; // null

And you can imagine how this applies to all kinds of type checks.

The map method has an interesting property: It allows to compose functions. Said otherwise:

const double = v => v * 2;
const increment = v => v + 1;
NumberBox(5).map(double).map(increment).value; // 11
// will have the same result as :
NumberBox(5).map(v => increment(double(v))).value; // 11

In fact, any map implementation must make sure to have this property. In a functional paradigm, we want to compose small functions into more complex ones.

There is another thing that I must make sure of: map must not have any side effects. It must only change the functor value, and nothing else. How can I make sure of this? By testing that mapping the identity function (v => v) returns the exact same functor. This is called the identity law.

NumberBox(5).map(v => v)) == NumberBox(5)

I will not check these laws for the other examples, but feel free to check them for yourself.

Maybe Another Example

maybe

Sometimes, making a function robust isn't about checking the input type, it's about checking that the input exists. Like when a function accesses a property:

const street = user.address.street;

To handle the case where user or user.address are not set, I need to add existence checks:

const street = user && user.address && user.address.street;

I can use the functor pattern to handle such cases. The idea is to create a placeholder to hold sometimes a value, and sometimes nothing. Let's call this functor Maybe.

const isNothing = value => value === null || typeof value === 'undefined';

const Maybe = value => ({
    map: fn => isNothing(value) ? Maybe(null) : Maybe(fn(value)),
    value,
});

Now it's safe to call properties on a non-object:

const user = { name: 'Holmes', address: { street: 'Baker Street', number: '221B' } };
Maybe(user).map(u => u.address).map(a => a.street).value; // 'Baker Street'
const homelessUser = { name: 'The Tramp' };
Maybe(homelessUser).map(u => u.address).map(a => a.street).value; // null

Tip: Calling the value property at the end isn't very natural in functional programming. I can augment the Maybe functor with a getter that accepts a default value as argument:

const isNothing = value => value === null || typeof value === 'undefined';

const Maybe = value => ({
    map: fn => isNothing(value) ? Maybe(null) : Maybe(fn(value)),
    getOrElse = defaultValue => isNothing(value) ? defaultValue : value,
});

As for accessing object properties, it can get even better using a get helper:

const get = key => value => value[key];

const getStreet = user => Maybe(user)
    .map(get('address'))
    .map(get('street'))
    .getOrElse('unknown address');

getStreet({
    name: 'Holmes',
    firstname: 'Sherlock',
    address: {
        street: 'Baker Street',
        number: '221B'
    }
}); // 'Baker Street'

getStreet({
    name: 'Moriarty',
}); // 'unknown address'

getStreet(); // 'unknown address'

Either Right or Wrong

What about error handling? If the function mapped on a functor throws an error, the functor should be able to handle it.

For instance, let's consider the code required to validate a user email. To be precise, I want to validate an email address only if it is given, otherwise, I ignore it and return null.

const validateEmail = value => {
    if (!value.match(/\[email protected]\S+\.\S+/)) {
        throw new Error('The given email is invalid');
    }
    return value;
}

validateEmail('[email protected]'); // '[email protected]'
validateEmail('[email protected]'); // throw Error('The given email is invalid')

I'm not fond of that throw, because it interrupts the execution flow. What I'd like is a validateEmail function that doesn't throw an error, but may return either a value or an error. How to do this with functors?

I need to return a different functor based on a try/catch: either a functor that allows me to continue to map, or a functor that would not change its value anymore (if it's an error). I'll call these two functors Left and Right. The validateEmail function would look like the following:

const validateEmail = value => {
    if (!value.match(/\[email protected]\S+\.\S+/)) {
        return Left(new Error('The given email is invalid'));
    }
    return Right(value);
}

The Left functor should never change its value anymore, whatever function is passed to its map:

const Left = value => ({
    map: fn => Left(value),
    value
});

Left(5).map(v => v * 2).value // 5

As for the Right functor, it simply applies a function to its value, transparently. You could call it the Identify functor, too.

const Right = value => ({
    map: fn => Right(fn(value)),
    value
});

Right(5).map(v => v * 2).value // 10

And now I can chain map calls on my email value:

validateEmail('[email protected]') // valid email, return Right functor
    .map(v => 'Email: ' + v)
    .value;  // 'Email: [email protected]'
validateEmail('[email protected]') // invalid email, return Left functor
    .map(v => 'Email: ' + v)
    .value;  // Error('The given email is invalid')

The Left and Right functors form what we call an Either.

Catching Errors

The validateEmail returns either a result or an error, but doesn't allow me to do something specific in case of an error. For instance, I may want to catch the Error and log its message property.

I'll add a catch() function to both the Left and Right functors to allow me to do just that:

// on a Right, catch() should do nothing
const Right = value => ({
    map: fn => Right(fn(value)),
    catch: () => Right(value),
    value
});
// catch is ignored on a right
Right(5).catch(error => error.message).value // 5

// on a Left, catch() should apply the function, and return a Right to allow further mapping
const Left = value => ({
    map: fn => Left(value),
    catch: fn => Right(fn(value)),
    value
});
Left(new Error('boom')).catch(error => error.message).value // 'boom'

For better reusability, I combine these Left and Right functors into a generic tryCatch function:

const tryCatch = fn => value => {
    try {
        return Right(fn(value)); // everything went fine we go right
    } catch (error) {
        return Left(error); // oops there was an error let's go left.
    }
};

And now, I can use this new tryCatch function to decorate the initial implementation of validateEmail, and get either a Left or a Right functor in return.

const validateEmail = tryCatch(value => {
    if (!value.match(/\[email protected]\S+\.\S+/)) {
        throw new Error('The given email is invalid');
    }
    return value;
});

validateMail('[email protected]')
    .map(v => 'Email: ' + v)
    .catch(get('message'))
    .value; // 'Email: [email protected]'
validateMail('[email protected]')
    .map(v => 'Email: ' + v)
    .catch(get('message'))
    .value; // 'The given email is invalid'

Combining Functors

Now, let's try to validate not an email string, but a user object that maybe has an email. I do not need to validate the email if the user has no email property.

Let's use Maybe and combine it with Either:

const validateUser = user => Maybe(user)
    .map(get('email'))
    .map(v =>  validateMail(v)
        .catch(get('message'))
    );

validateUser({
    firstName: 'John',
    email: '[email protected]',
}); // Maybe(Right('[email protected]'))

validateUser({
    firstName: 'John',
    email: '[email protected]',
}); // Maybe(Left('The given email is invalid'))

validateUser({
    firstName: 'John',
}); // Maybe(null)

I always get a Maybe, but its value is either a Left or a Right functor, or even a null value. So in order to get the value, I have to test Maybe value to know what to do with it.

const validateUserValue = user => {
    const result = validateUser(user).value;
    if (value === null || typeof value === 'undefined') {
        return null
    }
    return result.value;
}

This is bad: all the code I worked so hard to remove is coming back for revenge. You can imagine that it gets worse if I try to combine more functors, ending up with .value.value.value.value...

nested monad

How can I handle this crisis?

Chain To The Rescue

I need a method allowing me to extract a value of a functor, even though this value is wrapped inside another functor.

I need a flatten method that removes the inner functor, keeping only its value.

const Maybe = value => ({
    // we could return the value, but then we would sometimes switch functor type.
    // This way Maybe.flatten will always return a Maybe
    flatten: () => isNothing(value) ? Maybe(null) : Maybe(value.value),
    ...,
});

const validateUser = user => Maybe(user)
    .map(get('email'))
    .map(v =>  validateMail(v)
        .catch(get('message'))
    )
    .flatten()
    // since now I will always have a simple Maybe, I can use getOrElse to get the value
    .getOrElse('The user has no mail');

validateUser({
    firstName: 'John',
    email: '[email protected]',
}); // '[email protected]'

validateUser({
    firstName: 'John',
    email: '[email protected]',
}); // 'The given email is invalid'

validateUser({
    firstName: 'John',
}); // 'The user has no mail'

So now I can call .getOrElse and get the value directly. But that's still not ideal. You see, in functional programming, you often need to map and then flatten, or to flatMap for short. But the term "flatMap" describes what the procedure does, not what it is used for. This operation is used to chain functors, so let's call it that: chain.

const Maybe = value => ({
    flatten: () => isNothing(value) ? Maybe(null) : Maybe(value.value),
    // using the named function form instead of the fat arrow function
    // because I need access to this
    chain(fn) {
        return this.map(fn).flatten();
    },
    ...,
});

const validateUser = user => Maybe(user)
    .map(get('email'))
    .chain(v =>  validateMail(v)
        .catch(get('message'))
    )
    .getOrElse('The user has no mail');

validateUser({
    firstName: 'John',
    email: '[email protected]',
}); // '[email protected]'

validateUser({
    firstName: 'John',
    email: '[email protected]',
}); // 'The given email is invalid'

validateUser({
    firstName: 'John',
}).value; // 'The user has no mail'

Note that the two names are used in functional programming, so flatMap == chain.

Here Comes The Monad

By the way, a functor that can flatten itself with a chain method is called a Monad. So Maybe is a monad. Not so scary, right?

Like map, chain must respect a few laws to ensure that monads combine correctly.

  • Left identity

    Monad(x).chain(f) === f(x)
    // f being a function returning a monad

    Chaining a function to a monad is the same as passing the value to the function. This ensures that the encompassing monad is totally removed by chain.

  • Right identity

    Monad(x).chain(Monad) === Monad(x)

    Chaining the monad constructor should return the same monad. This ensures that chain has no side effect.

  • Associativity

    monad.chain(f).chain(g) == monad.chain(x => f(x).chain(g));
    // f and g being functions returning a monad

    Chain must be associative: Using .chain(f).chain(g), is the same as using .chain(v => f(v).chain(g)).

    This ensures that we can chain function that uses chain themselves.

Conclusion

You can play with the code of this tutorial in the CodeSandbox below. It uses a slightly modified example: try to understand why it's better than what was explained earlier.

I hope I have demystified functors and monads, and shown how they can be useful. Next time, I will see how we can use monad to handle side effect in a functional way.