map
and fmap
in HaskellHaskell’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.