The Monad Module I: Functors

The first datatype we encounter on our journey through Monad is that of Functor.

Functor has the following class definition:

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

Functor is desribed as being used for types that can be mapped over. According to wikipedia:

“A category is itself a type of mathematical structure, so we can look for “processes” which preserve this structure in some sense; such a process is called a functor.”

And so, of course, it is in Haskell. The most obvious case is for instances of [], such as [1,2,3] where fmap is aliased as map. The “mathematical structure” referred to above is the list. The concept of a list makes no presumptions about what it carries (which is why its type definition is simply [a]), only that those values are held in a list. So when we apply fmap f (where f is a function of the type (a -> b)) to a list we alter the values within the list, but not the structure - in other words we apply fmap to one list of values and get another list of values in return, i.e. [a] -> [b]).

This is the point of the Functor class. It specifies how to apply functions to an instance of Functor without changing its structure. Sometimes this idea is explained by asking you to think of Functor instances as being boxes that contain a value or a context within which those values reside. Both are relevant ways of thinking of it - the point is that fmap tells Haskell how to reach inside that box and manipulate the value whilst preserving the overall structure.

Functor also has two rules that its instances should follow, specifically:

fmap id  ==  id
fmap (f . g)  ==  fmap f . fmap g

The first, fmap id just says that applying fmap id to a functor should return the same as applying id to that functor. This makes sense when viewed from the perspective of “retaining strucure” since fmap is a process for changing a value without changing its structure, and its structure is the functor. Therefore fmap id applies id to the value without changing the surrounding functor, and id simply returns the functor and its value.

The second, fmap (f . g) == fmap f . fmap g says that applying fmap to two functions, f and g and then composing the output is the same as applying fmap to f . g. It works on the same principles mentioned above: fmap retains the structure of the value, and so fmap g accepts a functor and returns a value within the same functor, as does fmap f. The overall effect of this is the second functor rule.

Another good example of a functor is Maybe. Its implementation of fmap is as follows:

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

In both cases, the structure of a Maybe is retained.

Written with StackEdit.

Comments

Popular Posts