Functor #
The notion of functor comes from a branch of mathematics called category theory.
For programmers, a functor can be viewed as a generic type that implements a certain function (called fmap in Haskell), while respecting certain constraints.
In Haskell #
Definition.
Functorin Haskell is a type class.A type
tthat belongs to this class must:
- be a generic type (with a single type variable), and
- implement the function
fmap :: (Functor t) => (a -> b) -> t a -> t b
Terminology. The function
fmaplifts a function$\qquad$
f :: a -> bto a function
$\qquad$
fmap f :: t a -> t b
Example. The type “List” (noted
[ ]in Haskell) is a functor.So for this type,
fmaplifts a function$\qquad$
f :: a -> bto a function
$\qquad$
fmap f :: [a] -> [b]
Constraints #
The mathematical definition of a functor requires that fmap satisfies two additional constraints, sometimes called the functor laws.
Constraint.
fmapshould preserve function composition, i.e.$\qquad$
fmap (g . f)and(fmap g) . (fmap f)must be the same function.
Constraint.
fmapmust preserve the identity function , i.e.$\qquad$
fmap idandidmust be the same function.(where the first
idis overa, and the second overt a).
These two laws limit the possible implementations of fmap.
Example (continued). For the type “List”, the implementation of
fmapcoincides withmap.Indeed, this is the only way to implement
fmapwhile respecting the two functor laws.
So for instancefmap (+ 1) [1,2,3]evaluates to
[2,3,4], just likemap (+ 1) [1,2,3]
Usage #
Intuition. A functor can often be viewed as a “container” type.
Examples. “List”, “Set”, “Tree” and “Optional” are all functors.
Intuition. The functor laws often capture the “natural” (a.k.a. expected) way to lift a function to a container type.
Example. Consider the function
ord :: Char -> Intthat maps a character to its Unicode point.
We can lift it to a function
fmap ord.
Then:
- If
csis a list of characters, thenfmap ord csis the list of their Unicode points,- If
csis a set of characters, thenfmap ord csis the set of their Unicode points,- If
cis an optional character, thenfmap ord cis its optional Unicode point,- etc.
To go further.
Benefit. One can write generic code that uses
fmap, regardless of its implementation for a specific functor (for an illustration, we refer to Programming in Haskell).Other applications. Functors are used for automatic program verification.
Limitation. In general, the Haskell compiler cannot check whether an implementation of
fmapsatisfies the functor laws.
Maybe as a functor
#
Reminder.
Maybeis the option type of Haskell.It is declared as:
data Maybe a = Nothing | Just a
Implementation. The type
Maybeis a functor. Therefore it implementsfmap.So in this case,
fmaplifts a function$\qquad$
f :: a -> bto a function
$\qquad$
fmap f :: Maybe a -> Maybe bThis function maps
NothingtoNothing, andJust xtoJust (f x).Or in other words:
fmap :: (a -> b) -> Maybe a -> Maybe b fmap _ Nothing = Nothing fmap f (Just x) = Just (f x)
Examples. We can use
fmapto lift the function(+ 1).fmap (+ 1) Nothing -- evaluates to `Nothing`$\qquad$
fmap (+ 1) (Just 3) -- evaluates to `Just 4`
Usage #
For the functor
Maybe,fmaplets us propagate some “null values” (i.e.Nothing) without boilerplate code.
Example. Consider a function
maybeHead :: [Int] -> Maybe Intthat maps a list to its head (if present).
For instance:
maybeHead []evaluates toNothingmaybeHead [2,3,4]evaluates toJust 2Now let us assume that we want to:
- apply the function
(^ 2)to the output ofmaybeHeadif present, and- propagate the “null value” (a.k.a.
Nothing) otherwise.No need to check explicitly whether
maybeHeadoutputsNothing. Instead, we can usefmap.
For instance (assuming that we import>>>from the moduleControl.Category):maybeHeadSquared :: [Int] -> Maybe Int maybeHeadSquared = maybeHead >>> fmap (^ 2) -- compose `maybeHead` with `fmap (^ 2)`Then
maybeHeadSquared [3,4,5]evaluates to
Just 9, whereasmaybeHeadSquared []evaluates to
Nothing.
Evaluate the following expressions (assuming that we import & from the module Data.Function):
[3,4,5,6] & filter (> 6)
& maybeHead
& fmap (+ 1)
[3,4,5,6] & filter even
& maybeHead
& fmap (+ 1)
& fmap (< 5)
[[3,7],[4,5]] & map (filter even)
& map (maybeHead)
& map (fmap (+ 1))
-
Nothing
-
Just False -
[Nothing, Just 5]