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-only map function, and rename fmap to map instead? Well, that’s a good question. The usual argument is that someone just learning Haskell, when using map incorrectly, would much rather see an error about lists than about Functor.


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 mapfunction 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)

Tags:

Haskell