What does a functor actually look like in various programming languages? We already said it’s something you can use to map, so let’s take a look at some language’s mapping functions:
- Clojure has
- Haskell has
fmap. (It also has
<$>, which is the same as
- C# has
- Java’s streams library has
So, are any of these functor mapping operations? Well, it won’t take a genius to guess that
fmap does the right thing.
map in Haskell does the same thing, but only works for
Lists. The definition of
fmap for list is
map, so it’s pretty much a wash. (*)
Land of Compromises
The others? Well, kind of, but you tend to need to squint a bit. The problem is that if you map over an identity function, (e.g.
x => x in C#) you should get the same type back as you put in. And actually, that’s very rarely true.
map in Clojure can be called on a vector and will return a lazy list.
mapv can be called on a lazy list and get back a vector.
map in Java and
Select in C# send an interface type to the same interface type, but rarely return the exact same type as you were expecting.
Moreover, there isn’t a general mapping interface that lots of functor-like things implement and there isn’t any way to make one. This isn’t a problem for list comprehension, but it horribly breaks functors as a general model of computation. (#) You can still use the concepts, but you’ll end up with a lot of code duplication. Indeed, you’ll probably already have this code duplication and be thinking of it as a pattern. As is all to often the case, low level programming patterns reveal deficiencies in your programming language.
There’s good reasons for these mapping functions not behaving exactly like a functor, though: performance. The haskell compiler has everything, not just lists, as lazy and can optimize types away. Clojure, C# and Java can’t and treat them as hard optimization boundaries.
Haskell ain’t perfect either
We already established in the previous article that there are plenty of functors that have nothing to do with types. Haskell’s Functor type class is therefore only a functor on the category of Haskell types (usually referred to as Hask). This seems good enough, but actually it isn’t.
Consider a set of values. You can easily define a mapping function that satisfies the functor rules. Sadly,
Set in Haskell isn’t a Haskell Functor. This is because
Set imposes a condition on its values that they be sortable. Whilst this isn’t a problem for real Functor’s, it’s a problem for Haskell functors because type classes don’t admit restrictions on their type parameters. To put it another way,
Functor in Haskell is a functor over the whole of Hask, never a subcategory. For that matter, you can’t do the (*2) functor that I described last time in any sensible way because you can’t restrict its action to integers.
It turns out this problem is fixable, with Rank-2 typeclasses, but don’t hold your breath for that to land in Prelude any time soon. In the meantime, you can’t use
Functor to represent functors with domain type restrictions.
(*) Many smart Haskellers believe (and I agree with them(+))
map should work on all functors and
fmap should be retired. There’s a general theme here: the standard libraries are showing their age and need work to simplify them.
(+) If you’ve never seen Alan Rickman play Obadiah Slope, you’re missing out.
(#) If you’re prepared to lose information, all you really need is reduce/Foldable anyway.