Fantas, Eel, and Specification 6: Functor27 Mar 2017
Fantasy Landers, assemble! We’ve been
concatenating for two weeks now; are you ready for something a bit different? Well, good news! If you’re humming, “Oh won’t you take me… to functor town?”, then this is the article for you. Today, friends, we’re going to talk about functors.
Yes, so, at the very end of last year, I wrote an article about functors that went over several examples of
Functor types. It’s a collection of examples of how these can be useful in practice, but it’s a bit light on intuition. Let’s right that wrong today!
I’d recommend giving the above a quick read, as this post won’t spend much time going over what was said back then. tl;dr,
Functor is just another typeclass (a structure just like
Monoid) with one special method:
-- Any functor must have a `map` method: map :: Functor f => f a ~> (a -> b) -> f b
… and (surprise!) a couple of laws. For any functor value
u, the following must be true:
// Identity u.map(x => x) === u // Composition: u.map(f).map(g) === u.map(x => g(f(x)))
They might not be immediately clear, and the definitions in the other article are a bit wordy, but have a couple reads. In my head, I tend to think to myself, a call to
map must return an identical outer structure, with the inner value(s) transformed.
If this looks a bit weird to you, then it’s probably because
Functor is the first entry we’ve seen in Fantasy Land that must contain some other type.
String. It has actually ticked all the boxes up until now - it’s a
Semigroup, and a
Monoid - but we can’t make it a
Functor. This is because
String is just, well, strings!
Array a has also ticked all the boxes so far, but it is a
Functor. This is because it contains a value of type
a (or, usually, several of them). Strings are just strings - you can’t have a “
String of a thing” in the same way as you can have an “
Array of a thing” or a “
Maybe of a thing”.
Functor types are containers. Not all container types are functors. Want an example? Let’s look at
Set a - a collection of unique values of some type
a. There’s a catch here, though -
a must be a
Setoid. Looking at the type signature for
map, we see no
Setoid restrictions - we could
Set a values to some not-a-
Function), and we’d have no way of ensuring uniqueness!
A functor does not care about the type it’s holding.
Set, on the other hand, must. Thus,
Set can’t be a
Functor. Don’t worry, though - there are plenty of types that are functors!
Ok, everything has hopefully just about made sense up to here. Now’s when we get a bit… hand-wavey. Sorry in advance. We’ve been over the laws, and we’ve seen some examples. What’s the intuition, though? If all these wildly different types of things are functors, what behaviour do they all share?
Imagine a world without functors. All we have are basic values:
Number, that sort of thing. No
Array, though. No
Set, either, as you’d internally need an array of values. No nullable values at all (they’re not type-safe). No
Function, even! We’d have an extremely limited set of tools and would probably throw our computers out the window before too long. But, when we start introducing
||Represent a value.|
||Represent a possibly null value.|
||Represent a value or exception.|
||Represent a number of values.|
||Represent a mapping to values.|
A functor is like a little “world” in which our boring, functor-less language has been extended in some (hopefully useful!) way. When we put a value into one of these worlds, we say that we’re lifting the value into the functor.
More than one extension? What if we want to represent a number of mappings to values? We make an
Functions! What about a mapping to a possibly null value? We write a
Function that maps to
Maybes! If we want to combine these capabilities, we simply nest them.
We’ll see that we’re not always so lucky with composition-by-nesting when we get on to more complex structures!
We’re there. That’s what a functor does. It provides some extended behaviour, with total, gorgeous type safety. Note that our language isn’t changed inside a functor type - just extended. This is why the functor laws hold. Let’s write a couple of little proofs:
// For ANY functor *constructor* U: // e.g. [x].map(f) === [f(x)] U(x).map(f) === U(f(x)) // Read: `map` just applies the function to // the inner value. Using only this rule, // we win! const id = x => x // Identity... U(x).map(id) === U(id(x)) === U(x) // id(x) === x // Composition... U(x).map(g).map(f) === U(g(x)).map(f) === U(f(g(x))) === U(x).map(x => f(g(x)))
Our laws hold, everything works. If it helps your intuition, just think of
U(x).map(f) === U(f(x)) every time you run into a functor. More generally (and correctly),
map applies a function to the value(s) within a functor and nothing else. That, friends, is all there is to it!
So, I’m sorry that it got a bit less clearly-defined towards the end. You can see it’s quite difficult to define an all-encompassing intuition for functors, but I hope this has given you somewhere to start!
If you want more examples, the Haskell docs contain loads of Functor types for you to see! The intuition will come with time. Until then, as always, feel free to tweet me and I’ll do my best to clarify my ramblings.
Next time, we’ll look at another type of functor: the contravariant functor. Get excited! As always, until then,
Take care ♥