Functors: I was WRONG! (FunFunFunction Video #11)

Script is below, but, as usual: You should watch video, a lot is lost in just text.


Good monday morning!

This is FunFunFunction, a monday morning show where we try to become more confident and excited about programming by exploring old wisdom, wild ideas and having fun.

Prerequisites

In order for this video to make sense, you need to have watched my previous video on Functors. It’s linked in the description. You also need to be familiar with Map, and Promises — videos about those are also linked in the video description in case you’re feeling rusty.

Functor means an object that has .map

I would like to geek out on the definition of Functors for one more video, because there has been a really good response to the the last video, and there has also been some great corrections on it.

I’ve gotten a lot of feedback from some wonderfully nice people, especially @drboolean on Twitter has been very helpful, you should really follow him if you’re interested in functional programming. Today, I’m going to try to take a stab at a more accurate explanation than the one I made in my previous video.

In the last video I said that functors are functions like map, that is not correct — Functors are objects that have a map method. So, for instance, it’s Array that is a functor because it is an object that has an implementation of map. It’s not map itself that is the functor — it’s the object implementing map.

The most common example of a functor in the world is the JavaScript array. Other objects that are often implemented as functors are Promises, Streams and Trees. So you can think of functors as the generic equivalent of Array having a map method. Things in general, not just arrays, that we can map.

In the last video, I mentioned that Array#filter is a functor, which is completely wrong. Filter has nothing to do with functors, it’s objects that implement map that we’re interested in.

The reason I got confused about the word functor referring to the function itself is that in the subset of math that is called category theory, functors actually are functions. Also, in computer science in general, functor is sometimes used as a synonym to higher-order function.

But 95% of the functional programming community, including Clojure, Haskell, Scala, F#, settled on the definition that objects that implement map are functors, and that’s the definition we’re going with in this video.

A reasonable implementation of map

So exactly do we mean when we say a functor should have a map method? You cannot just implement map and have it return bleargh and call it a functor. There needs to be some gooddamn rules.

If I had a bigger beard, I would now refer to the functor laws in Haskell. The functor laws are great, but I think that they sacrifice understandability in order to be terse and correct. It’s good to check them out eventually, but you know, the HTML spec is not a good place to start learning web development either.

Instead, lets have a look at the Functor that we all know and love, Array, and look at what it does that makes it qualify as a functor.

const dragons = [
{ name: 'Fluffykins', health: 70 },
{ name: 'Deathlord', health: 65000 },
{ name: 'Little pizza', health: 2 },
]
const names =
dragons.map(dragon => dragon.name)
console.log(names)

Output:

[
'Fluffykins',
'Deathlord',
'Little pizza'
]

So here we have an array of dragon objects, and we use map to just get the names of the dragons. Again, I’d like to stress here that map itself is not what we refer to with the word Functor, it is Array that is a Functor because it has a map method.

Let’s talk about three crucial things that Array#map does in order to qualify Array as a Functor. But first, I must show you the worst thing ever.

(Showing electric santa hat)

Where were we? Yes, let’s talk about three crucial things that Array#map does in order to qualify Array as a Functor.

1. Transformation of contents

The basic idea is that the map method of the functor takes the contents of the Functor and transforms each of them using the transformation callback passed to map. In this case, this function here is the transformation callback — it transforms a dragon object into just a dragon name. This is the first barrier of entry that Array passes in order to be called a Functor. Some people like to point out that Array#map is also passed the array index in addition to just the value, which stretches the contract a bit, but there is a point where a secret club becomes so secret that it only has no members, so, let’s give Array some slack.

Promises are often functors. The promises built into ES6 do not have a map methiod, but most promise libraries do, for example, if you use Bluebird, you can do this:

import Promise from 'bluebird'const whenDragonLoaded = new Promise((resolve, reject) => {
// fake loading
setTimeout(() => resolve({
{ name: ‘Fluffykins’, health: 70 }
}, 2000)
})
const names =
whenDragonLoaded
.map(dragon => dragon.name)
.then(name => console.log(name))

Output:

'Fluffykins'

If we look at this code, we create a Promise that yields a dragon object after two seconds. When we have it, we map the name, and then write it to the console.

Note that the map callback is exactly the same as in the previous example. In that example, we saw an array functor shielding the transformation callback from the complex reality that there are more dragons than one, while the Promise functor protects the transformation callback from the complex reality that there isn’t any dragon until later.

I would like to make a small detour and point out that this is a really good example of what functional programming is all about — breaking problems into teeny tiny super simple functions and then putting them together using cool glue like Functors. It allows us to separately create solutions for the problem of iterating items, the problem dealing with items coming at a later time, and the problem of getting out the name of those items. Once we have those solutions, functional programming allows is to compose them together, which allows us to re-use way more code than we would otherwise be able to.

2. Maintain structure

The second thing that Array#map does in order qualify Array for the title of Functor is that it maintains structure. If you call .map on an array that is three long, it returns an array that is three long. It never changes the length of the array, it doesn’t return null. Like you see in the example, we transform the individual values contained in the array, and even change their types, but map cannot alter the structure of the array itself.

This is why the string functor in the last video is an incorrect example. It gets the intuitive spirit of a functor right, so if that example made you “get” functors, don’t worry, you don’t have to erase that part from your brain.

But string doesn’t work as a functor, even if we give it a map method, because Functors need to be able to handle any type. Functors are meant to be generic containers, like Array, Stream, Tree or Promise. String doesn’t work, because it can only contain characters.

A String with a map method welded on gets the spirit of the functor right, but since it’s for characters only, it doesn’t qualify as a functor. A functor needs to be a container for any object.

3. Returns a new functor

The third and final thing that Array#map does in order to be functor-material is, the value that map returns must be a functor of the same type. Because of this, we can chain map calls like this:

const dragons = [
{ name: ‘Fluffykins’, health: 70 },
{ name: ‘Deathlord’, health: 65000 },
{ name: ‘Little pizza’, health: 2 }
]
const nameLengths =
dragons
.map(dragon => dragon.name)
.map(dragonName => dragonName.length)
console.log(nameLengths)

Output:

[ 10, 9, 12 ]

Here we have the same array of dragons, but after we extract the names, we get the length of each name. Because the first map function returns a functor, we can keep calling map on it. You can also do map map map map chaining with promises, or any other functor.

In summary: A functor is an object that has a map method. Arrays in JavaScript implement map and are therefore functors. Promises, Streams and Trees often implement map in functional languages, and when they do, they are considered functors. The map method of the functor takes it’s own contents and transforms each of them using the transformation callback passed to map, and returns a new functor, which contains the structure as the first functor, but with the transformed values.

Today has been a little dry an theoretical, I know. We’ve explored the definition of a functors even more, and we’ve looked even more at the the most common functor, array. But in the next episode, we’re going to explore the coolest and most underused functor, Streams. As usual, that episode will be released monday morning 08:00 GMT. Do not miss it, put it in your calendar!

Until next monday morning, stay curious.

Mattias Petter Johansson

Written by

Creator of Fun Fun Function, a YouTube show about programming.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade