/ functional programming

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.

What the Functor?
Share this

Subscribe to Developing Thoughts