elm christmas

map is for lists, isn't it?

←Previous postNext post →

A 3 min read written by
Aksel Wester

You use map to traverse a list and apply a function to all of its element. The type signature of List.map says that it takes two arguments: a function to use on the elements in a list, and an actual list of elements:

List.map : (a -> b) -> List a -> List b

But the map function doesn't just exist in the List module, there are other maps as well. Array.map makes sense, Arrays are basically just lists anyway. Their type signatures are even almost exactly the same:

Array.map : (a -> b) -> Array a -> Array b

But what about all these other maps? Maybe.map, what does that do? A Maybe isn't a collection of elements like List and Array! Well, it is a collection, but it only contains one element, which might not be there. Let's look at the type signature of Maybe.map:

Maybe.map : (a -> b) -> Maybe a -> Maybe b

Hmm... Strange... That seems awfully familiar. Anyway, let's try to reason about what this function does! We start with the last argument 'Maybe a'. Since the a is lower case, we know that it can be whatever type we want, as long as the a is the same in the whole type signature. The first argument, (a -> b), is a function that takes an a and returns a b. In other words a function that we can use on the element that might or might not be present in our Maybe. Lastly, the return type is Maybe b; a Maybe of the same type that the function (a -> b) returns.

So what does Maybe.map actually do? If the Maybe in the last argument is a Nothing we can't really do anything, so map has to return another Nothing. Because the type b be any type, the implementation of Maybe.map can't possibly know in advance how to construct that type. But what if the argument is a Just? Well, then it's a Just containing something of type a, so what do we do with that? Since the return type of Maybe.map is a Maybe b, map has to use the function (a -> b) on the a to get a b, and then wrap it in a Just.

In other words, Maybe.map takes a function and a Maybe and uses that function on the element in the Maybe if the element is present. If we look in the source code for Maybe.map, we see exactly what we expect:

map : (a -> b) -> Maybe a -> Maybe b
map f maybe =
    case maybe of
      Just value -> Just (f value)
      Nothing -> Nothing

In fact, if we look at all the other map functions on other modules, they all share the same pattern in their type signatures. They all do what we expect, or what we can guess, by examining their stuctures.

In summary, a the map function of some structure Structure looks like this:

Structure.map : (a -> b) -> Structure a -> Structure b

And map uses the function provided as its first argument on the element or elements the structure contains.

P.S.: What we here call a structure is called a functor in category theory, and being a functor is one of the requirements of a monad.

←Previous postNext post →