Pragmatic Functors

or how I learned to stop worrying and love the Box

Sean May
10 min readDec 25, 2017

Prologue

This was originally intended to be an email. That it has turned into a novella ought to bring insight into why I write so few emails.

Regardless, a lot has been said to mystify and demystify this concept. I figured that instead of finishing this email, I would throw my hat into the ring, and also attempt to produce some content at the same time.

A functor is little more than a conceptual box you can put things in, such that you can operate on arbitrary values in predictable and composable ways. Functors hide the details of their own operating contexts, and provide a predictable API, in the same way that [].map(f) hides a loop and array management, and simplifies your code in the process.

If we were going to convert some string of CAD to USD, in a bespoke way, we might write something like:

There are some money-specific bugs in there, and some money-specific concerns, but the point is, of course, the code.

We have a lot of domain-specific, and even instance-specific detail baked into this solution. If the algorithm changes, we might find ourselves lost, trying to figure out how to rename pieces, or find names for the new half-steps that need to be injected.

If this were a list of items that needed to be worked on, instead of one item, we know that we could chain some of these operations together, and compose an algorithm that way. So why not cheat for a second and treat the input as a collection of 1 item?

In this pipeline, we can start extracting some of those functions, to be generalized as pure functions. We can partially apply, where needed. The functions that get taken out and named could get unit tests; the whole algorithm itself could get a unit-test or ten.

We can also start to see not only some of the holes we missed before (like, say, padding the currency output), but we can also see where in the pipeline those operations should be placed.

A touch of refactoring and we might just have an algorithm that is both provably correct and applicable to any combination of sufficiently similar currencies. As new realizations come through, and new requirements come through, we can go back to the pipeline and swap functions in and out, adding, removing, and reordering as needed.

We used Array.of(cadInput) here, but for the sake of boxing, we could just as easily have used Promise.resolve(cadInput) or Observable.of(cadInput), and chained via .then or .map respectively.

All of these are Functors because they wrap around a value and provide a .map (or equivalent) method. There are laws about purity and associativity, that may be brought up at some other point, but basically, as long as there are no “super-awesome, helpful, time-saving” things your map method does under the covers (that all programmers seem to bake into methods) and you can’t tell the difference between [2].map(triple) and [triple(2)], you have a Functor. There is another requisite you should know about functors, though: after they map over a value, they need to return the same kind of box, with the new value inside.

Array<A>.map(A->B) must return Array<B>.
Promise<A>.then(A->B) must return Promise<B>.

This is easy to take for granted, and forget about, but it’s a key distinction. If I map over an array, and the return value is not a new array, then I have broken the user’s ability to chain list operations. If Promise#then ever returned something that was not a promise, it would break the concurrency model.

Functor Kinds

There are different kinds of functors, but the few that you are most immediately familiar with are going to be Array, Observable, Promise and Identity.

If you haven’t been through these articles before, you likely haven’t had any formal introduction to Identity as a Functor. That in mind, we just hijacked Array above to serve as a stand-in for Identity. You’ve also used Identity and the like in other places, without paying any mind to it.

> some-command | grep some-expression | some-other-command

If a Functor represents an execution context which allows you to map a function over the current value, and provides a new execution context, with the new values, ready to be mapped over, then Unix piping is providing literal execution contexts, implicitly. Elixir and friends (perhaps soon to include JS) supply similar magic with the |> pipeline operator.

This is an implicit, or “autoboxed” `Identity` functor (among other things you might call it).

Without the syntactic sugar, we can implement the exact same thing like so:

This leaves me with the slightly less sexy, but equivalent

I’ve now got a Functor that is ready to wrap and transform nearly any kind of value you could think to operate on.

So if the Identity functor is this useful for composing values and operations, why would we even need another kind of functor? The reality is that while Identity is perfect for modifying values in a concise way, there are things that it’s not really meant to do.

  • If you wanted to start a chain on a value you don’t have yet, it will break, so Concurrency is an issue
  • If you have a collection of items that you want to transform at the same time, then you would have to loop over each item in the collection, to save the transformed values… so List management is an issue
  • If you have a stream of items that you want to transform, in sequence, Identity is only equipped to deal with one, so Stream management and Eventing are issues
  • If you have a value that is potentially null, or you have a process that might throw an error, Identity will be unable to protect you

That’s where other Functor types come into play.

  • Promise is a functor(ish) which allows you to asynchronously prepare a value, before triggering your chain of operations (mapping via the then method)
  • Array is a functor whose value is a collection, and whose map is applied to each element in the collection
  • Observable is a functor(ish) whose value is a stream, and whose map is applied to each element in the stream, as they pass through
  • Maybe (aka: Option) is a functor which allows you to deal with applying operations to values which might be missing, with safe no-ops in the Null case; Either is a functor to deal with cases where you might have to apply operations on a value, or propagate an error

In each case, the similarity to Identity should be clear; I want to map over a value, and return an object that lets me map over the result. Each additional Functor specializes in what it does, and how it does it. All of those implementation details are locked away in the bowels of the Functor, so that we can simply pick the correct Functor for the context, and use a familiar API

f<A> map(A->B) => f<B>

Let’s look at implementations of two of the above examples of Functors: Array and Maybe.

Array

Okay, in JS, we aren’t going to have a good time if we call this one “Array”, so let’s call it “List” instead.

This isn’t providing us any real value over just using native Array, but we can see both that the implementation is similar to and divergent from Identity, and also that there isn’t a whole lot going on under the hood, here. When we move into the territory of Monads, List will become more helpful, until ES20XX extends Array to be monadic, itself.

Maybe

This one is going to be a bit of a trip. Maybe is meant to work similar to Identity, except that it needs to handle missing values.

Let’s have a look at this in action

On to the implementation. If you haven’t already read the twist ending of this particular story, your head is probably thinking about something like

We know that we always need to be returning a Functor, so that we can always be chaining, but if we use that implementation, it might cause edge-cases that we haven’t considered. And if we’re using a type-system, it would be even better if we had the benefit of our types being able to tell what path we were on.

So instead of burying the two cases inside of `map`, let us instead pull the two completely different cases out, and make two different “kinds” of Maybe. We will have a Just kind (“I just have ‘$10.00CAD’. ”) and a Nothing kind (“I’ve got Nothing”). To the implementor, this might seem weird, but to the end user, they shouldn’t notice the difference.

If you hop back and forth between the implementation and the usage, you should notice that null checks in your system could now just be reduced to a fromNullable call, and you can happily map over that value, again and again, and simply provide a default value, in the case that your item didn’t exist in the first place.

Fun Fact: you can replace Just and Nothing with an array of 1 item, and an array of 0 items.

const fromNullable = x => valueExists(x) ? [x] : [];
fromNullable(null)
.map(x => x * 2)
.map(x => x + 1)
[0] || 9 // 9

…but don’t do that to your consumers.

Specialization

We’re starting to see that some of these Functors might have extra methods on them. That’s entirely fine. The extra methods are related to the special abilities that each Functor might have. Depending on the library, these might be handled in a generic way, or a very speicific way. Using Maybe as an example, you might have a property like isNothing where Just would return false and Nothing would return true. This kind of property might make it easier for your Functoral code to interface with an imperative world on the outside. You could put your functor in an if(maybeX.isNothing) block, or in a JSX render function, if need be). The extra properties and methods might be about other special powers a given kind of Functor shares with similar kinds (Functors that are “Applicative”, or “Traversable”, or other things that you will hear about, on your journey). The key is that no matter what extra bells and whistles a Functor has, it will always have the most common parts of the API.

With that in mind, lets have a look at a couple more things in list of problems that Identity can’t fix. We should have a look at Either and a replacement for Promise.

Either

With Maybe firmly in hand, we should tackle Either in a similar way. The purpose of Either is to let you map over a value, or propagate an error, without having to care which one you’re doing, until the last possible moment. It doesn’t necessarily need to be an error, it could represent any disjointed union (A|B number OR string), but for your sanity, consider it to be “Unhappy Path” OR “Happy Path” data-types.

Because we have two cases for behaviour, we’re going to want to split the concept of our Either into two kinds; Left and Right.

Think of it like a cross-roads; either you go left, or you go right. You can also think of it like a simple if/else. If we have the thing I need, then I can carry on and we’re all right; else there is a problem, and there is nothing left that I can do but tell you about it.

The implementation might be as follows

In languages with strong pattern-matching capabilities, you might even be able to map over errors and map over values successfully, without making your code messy; in this realm, however, we’re a little stuck, without a bunch of “Are you Left?” checks, and Left (in this example code, at least), stays in statis, until the onLeft function is called at the end of the chain. We could implement specialty methods, to operate on the Left side specifically, but that’s not necessarily something we want to go chasing after, here. Needless to say, this example is definitely simplified over what it could be, but also, will do the job.

Future

Okay; I pulled a bit of a bait and switch, here. I promised Promise, so what is this Future thing, and why is it taking its place?

The reality is that `Promise` as it exists in the ES6 spec is both not particularly FP-friendly, and also is a huge pain to write an implementation for. If you have a look at the algorithm for the A+ Promise spec, there are a lot of crazy checks going on under the hood, in order to make the thing more magical than it needs to be, and it ends up being 120+ lines of code, generally.

I’ve got a heavily-commented implementation of just such a thing, if you find yourself so inclined.

It works, and that’s great, but A+ Promise might be a bad example to include in this writeup. Instead, we can look at something that is almost exactly the same. Almost. Just swap Promise for Future and then for map, and you will almost know how to use this in production.

Okay, that map might look a little funky, but let’s break it down:

You can picture the map sort of like recursion. When some task is run, it’s going to run the task that came before it, all the way up to the original (probably async) task. When the original task resolves (or rejects), that resolution triggers the next resolution, all the way back down to the task that triggered the whole chain.

But those are implementation details, remember? That’s the stuff that we’re hiding behind a common API, so we don’t have to think about it as consumers.

Ready to see it in action?

Here’s the fun part, that people using RxJS are already used to; that buildValue instance of Future will never run any of the async code; never call servers; never open DB connections, or read files, until somebody calls fork, and provides the error and success handlers.

If you look at map and think about the recursive callstack, you should see why that’s the case.

If we flatten the internals out, we really have something like:

That implementation of `Future` as a functor is 9 lines of code, including an empty line. That’s a lot of power for so few characters, and it’s all tucked away behind that same “mappable” interface.

Epilogue

Hopefully, at this point, you have a reasonable understanding of what Functors are… or at least, what they aren’t. They aren’t a base-class, or a super-complicated equation. They aren’t even object structures with methods, necessarily. You could write map as a function that took in a function and a functor to run it on. They are some conceptual types of work that can be generalized, so that you can do that same type of work on top of all kinds of different data, and every one of those conceptual tasks can share a common execution pattern, using an interface as simple as map(A->B).

We still haven’t solved all of the world’s problems… and we’ve invented a few… What if I have a line like the following?

Future.of(5).map(x => Future.of(x * 2));
Now we have Future<Future<number>> to deal with.

That is definitely a problem. But we will handle that and more, in a forthcoming abandoned email:
The Monad Missive

--

--