Project Inquiry
Published on

map and fmap in Haskell

Haskell’s standard module ships with two functions, called map and fmap. The first one, map, is the typical function we are all used to in functional programming. Looking at its definition, reveals that it’s recursive implementation is exactly what one would expect:

map :: (a -> b) -> [a] -> [b]
map _ []     = []
map f (x:xs) = f x : map f xs

Based on foldr, we can write an identical function with an even shorter defintion:

map' :: (a -> b) -> [a] -> [b]
map' f = foldr ( (:) . f ) []

It’s important to avoid tail recursion, because that would make lazy evaluation impossible, which is a prerequisite for dealing with infinite lists. That’s why foldl is a bad idea, even though it would be faster (assuming the compiler performs tail call optimization).

In addition to map, there’s the Functor class, whose instances are required to implement fmap:

class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b

As it turns out, [] is an instance of Functor:

instance Functor [] where
  fmap = map

So, at least for lists, there’s no difference between the two. However, there are many more Functor instances. For example, let’s examine the instance for Maybe:

instance Functor Maybe where
  fmap _ Nothing  = Nothing
  fmap f (Just a) = Just (f a)

If the value we are mapping over is Nothing, it returns Nothing. Otherwise, it’s applying the given function f to the value from the Just and returns another Just with f’s return value.

Personally, this reminds me of ActiveSupport’s Object#try method. The Functor instance for Maybe is encapsulating exactly the same pattern, but in a more abstract way. That’s because the implementation can be completely different for other types.

The bottom line is that fmap is a generalized version of map. You can fmap whatever you want, as long as there’s a Functor instance for its type. This is extremely powerful, because it allows us to push complexity from the caller to the callee.

From now on, I’m going to use fmap exclusively in my own code: Code that’s using more generalized functions is easier to reuse and change later on.