What's the point of map in Haskell, when there is fmap?
I would like to make an answer to draw attention to augustss's comment:
That's not actually how it happens. What happened was that the type of map was generalized to cover Functor in Haskell 1.3. I.e., in Haskell 1.3 fmap was called map. This change was then reverted in Haskell 1.4 and fmap was introduced. The reason for this change was pedagogical; when teaching Haskell to beginners the very general type of map made error messages more difficult to understand. In my opinion this wasn't the right way to solve the problem.
Haskell 98 is seen as a step backwards by some Haskellers (including me), previous versions having defined a more abstract and consistent library. Oh well.
Quoting from the Functor
documentation at https://wiki.haskell.org/Typeclassopedia#Functor
You might ask why we need a separate
map
function. Why not just do away with the current list-onlymap
function, and renamefmap
tomap
instead? Well, that’s a good question. The usual argument is that someone just learning Haskell, when usingmap
incorrectly, would much rather see an error about lists than aboutFunctor
.
They look the same on the application site but they're different, of course. When you apply either of those two functions, map
or fmap
, to a list of values they will produce the same result but that doesn't mean they're meant for the same purpose.
Run a GHCI session (the Glasgow Haskell Compiler Interactive) to query for information about those two functions, then have a look at their implementations and you will discover many differences.
map
Query GHCI for information about map
Prelude> :info map
map :: (a -> b) -> [a] -> [b] -- Defined in ‘GHC.Base’
and you'll see it defined as an high-order function applicable to a list of values of any type a
yielding a list of values of any type b
. Although polymorphic (the a
and b
in the above definition stand for any type) the map
function is intended to be applied to a list of values which is just one possible data type amongst many others in Haskell. The map
function could not be applied to something which is not a list of values.
As you can read from the GHC.Base source code, the map
function is implemented as follows
map _ [] = []
map f (x:xs) = f x : map f xs
which makes use of pattern matching to pull the head (the x
) off the tail (the xs
) of the list, then constructs a new list by using the :
(cons) value constructor so to prepend f x
(read it as "f applied to x") to the recursion of map
over the tail until the list is empty. It's worth noticing that the implementation of the map
function does not rely upon any other function but just on itself.
fmap
Now try to query for information about fmap
and you'll see something quite different.
Prelude> :info fmap
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
...
-- Defined in ‘GHC.Base’
This time fmap
is defined as one of the functions whose implementations must be provided by those data types which wish to belong to the Functor
type class. That means that there can be more than one data types, not only the "list of values" data type, able to provide an implementation for the fmap
function. That makes fmap
applicable to a much larger set of data types: the functors indeed!
As you can read from the GHC.Base source code, a possible implementation of the fmap
function is the one provided by the Maybe
data type:
instance Functor Maybe where
fmap _ Nothing = Nothing
fmap f (Just a) = Just (f a)
and another possible implementation is the one provided by the 2-tuple data type
instance Functor ((,) a) where
fmap f (x,y) = (x, f y)
and another possible implementation is the one provided by the list data type (of course!):
instance Functor [] where
fmap f xs = map f xs
which relies upon the map
function.
Conclusion
The map
function can be applied to nothing more than list of values (where values are of any type) whereas the fmap
function can be applied much more data types: all of those which belongs to the functor class (e.g. maybes, tuples, lists, etc.). Since the "list of values" data type is also a functor (because it provides an implementation for it) then fmap
can be applied to is as well producing the very same result as map
.
map (+3) [1..5]
fmap (+3) (Just 15)
fmap (+3) (5, 7)