What the Functor?

A week ago I wrote an article on implementing Maybes in JavaScript, and while it was generally well received, it did draw some (well justified) criticism from some commenters. Specifically, there are some well-defined mathematical rules that my Maybe didn't obey. Terminology and nomenclature are always important if we want to communicate effectively with others, so although my Maybe has some utility, I wanted to learn some more about these mathematical rules so that:

  • Code I write is interoperable with existing functional libraries
  • I can understand the writing of others
  • My writing is understandable to others
  • It's interesting and fun!

One of the recommendations I received was to look at the Fantasy Land Specification which defines a set of algebraic data types. This a very technical specification and I'm not afraid to say parts of it go over my head at the moment, so I started with the most basic types. One of the simplest, and yet seemingly most commonly used and well known, is the Functor.

If a type is to become a functor, it need only do one thing: it must have a map method. This method has one argument (arity-1), which must be a function, and must return another functor of the same type. I use TypeScript a lot, and so we can define an interface for a functor in that language:

interface Functor<T> {
    map: <U>(fn: (a: T) => U) => Functor<U>;
}

We can now make concrete implementations of data structures that implement this interface, and be confident that if type-checking passes then we are obeying the rules. We'll start with a simple type that will wrap a number, and give us a simple way to inspect the value:

class NumberFunctor implements Functor<number> {
    private value: number;
    
    constructor(value: number) {
        this.value = value;
    }
    
    inspect() {
        console.log(this.value);
        return this;
    }
}

We now need to implement a map method - this method will take a function, pass it the internal value we are holding, and return a new instance of NumberFunctor:

class NumberFunctor implements Functor<number> {
    private value: number;
    
    constructor(value: number) {
        this.value = value;
    }
    
    inspect() {
        console.log(this.value);
        return this;
    }
    
    map(fn) {
        return new NumberFunctor(fn(this.value));
    }
}

It can be used like this:

const twenty = new NumberFunctor(20);

twenty.map(x => x + 1).inspect(); // 21
twenty.map(x => x * 2).inspect(); // 40

Because we are returning a new functor each time, we can also chain map calls together:

const fifty = new NumberFunctor(50);

fifty
    .map(x => x * 2)
    .inspect()        // 100
    .map(Math.sqrt)
    .inspect();       // 10

The key point is that when a type implements Functor, it means that it has a way of transforming the value(s) that it contains. When we use the map method we don't have to care about how this transformation occurs - that is the internal responsibility of the specific functor.

To illustrate this further let's make a very simple Tuple functor that will hold two values - a first and a second:

class Tuple implements Functor<number> {
    private first: number;
    private second: number;
    
    constructor(first: number, constructor: number) {
        this.first = first;
        this.second = second;
    }
    
    inspect() {
        console.log(`Tuple: (${this.first}, ${this.second})`);
        return this;
    }
}

How are we going to implement map for our tuples? We're going to apply the provided function to each of the values we hold, and return a new tuple with the results:

class Tuple implements Functor<number> {
    private first: number;
    private second: number;
    
    constructor(first: number, constructor: number) {
        this.first = first;
        this.second = second;
    }
    
    inspect() {
        console.log(`Tuple: (${this.first}, ${this.second})`);
    }
    
    map(fn) {
        return new Tuple(
            fn(this.first),
            fn(this.second)
        );
    }
}

We can use it just like the NumberFunctor:

const tuple = new Tuple(49, 100);

tuple.map(x => x * 2).inspect(); // Tuple (98, 200)

tuple.map(Math.sqrt).inspect(); // Tuple (7, 10);

Hopefully at this point you are thinking about where we have seen a map method before, and perhaps you realised you have used Array#map:

[1,2,3].map(x => x * 2); // [2,4,6]

It's easy to see now that JavaScript arrays implement the Functor interface, and they implement a map method that applies the function to each of the values they hold, and return a new array.

Thanks to everyone who read and commented on the original Maybe article, especially to those who wrote insightful and constructive comments.