Maybe as an Applicative Functor
Maybe
type is made an instance of the Applicative
type class as follows:
instance Applicative Maybe where
pure = Just
Nothing <*> _ = Nothing
(Just f) <*> something = fmap f something
It took me a several attempts before I could parse this definition. I was particularly puzzled by the line (Just f) <*> something = fmap f something
. To help me understand this better, I decided to build this definition back up from the very basics.
First, let’s look at the Functor
type class definition:
class Functor f where
fmap :: (a -> b) -> f a -> f b
This means that for a given functor type f
(i.e. type that is an instance of the Functor
type class), fmap
takes a function from a -> b
and a functor (box) that contains a
and returns a functor (box) that contains b
. An intuitive way to think about this is that fmap
opens the box containing a
and applies the function a -> b
to it, which results in b
.
Now let’s see how Maybe
is an instance of the Functor
type class:
instance Functor Maybe where
fmap func Nothing = Nothing
fmap func (Just x) = Just (func x)
- Line 2: Applying a function to
Nothing
results inNothing
. - Line 3: From the definition of the
Functor
type, we know that the type offunc
isa -> b
andJust x
corresponds tof a
. Applying the function tox
inside the box results in a value of typeb
in the functor box.
Now, let’s look at the definition of the Applicative
type class:
class (Functor f) => Applicative f where -- 1
pure :: a -> f a -- 2
(<*>) :: f (a -> b) -> f a -> f b -- 3
For a type f
that is an instance of the Applicative
type class, here is what each line means:
f
must also be a functor (i.e. be an instance of theFunctor
type class).- The
pure
function takes an arbitrary typea
and brings it into the functor. i.e.pure
putsa
in a box of typef
. <*>
takes a functor (box) of typef
that contains a function of typea -> b
, and a functor (box) of typef
that contains typea
. It results in a functor (box) of typef
that containsb
.
With the preamble out of the way, let’s make Maybe
an instance of the Applicative
type class. To do that, I need to implement the pure
and <*>
methods for the Maybe
type. Below is a line-by-line implementation:
instance Applicative Maybe where
pure x = Just x
For the Maybe
type, pure
simply wraps an arbitrary type in Just
, thus making it a Maybe
value. E.g. writing pure 4 :: Maybe Int
in GHCi results in Just 4
.
(<*>) Nothing _ = Nothing
Here, Nothing
maps to f (a -> b)
from the Applicative
class definition. We cannot extract a function out of Nothing
, so the result will be Nothing
regardless of the second argument.
(<*>) (Just func) Nothing = Nothing
In the line above, (Just func)
maps to f (a -> b)
, and Nothing
maps to f a
from the class definition. <*>
extracts func
out of Just func
, and applies it to Nothing
. Applying a function to Nothing
results in Nothing
(or, using the box analogy, applying a function to an empty box results in an empty box)
(<*>) (Just func) (Just x) = Just (func x)
(Just func)
maps to f (a -> b)
, and Just x
maps to f a
from the Applicative
class definition. <*>
extracts the function from Just func
, and applies it to x
inside the Just x
box. The result is Just (func x)
.
Now, let’s put the definition of <*>
for Maybe
type next to the definition of the fmap
function:
(<*>) (Just func) Nothing = Nothing
(<*>) (Just func) (Just x) = Just (func x)
fmap func Nothing = Nothing
fmap func (Just x) = Just (func x)
This makes it obvious that in the definition of <*>
, once we extract func
out of Just func
, we simply map that function over the second argument of <*>
(which will be of Maybe
type as well). This means that the <*>
implementation for Maybe
can be re-written as:
<*> (Just func) something = fmap func something
This is exactly how the <*>
function is implemented at the beginning of this blog post.
Finally, Functors, Applicatives, And Monads In Pictures by Aditya Bhargava is one of the best posts I’ve read on functors & applicatives. I highly recommend it.