Maybe
type is made an instance of the Applicative
type class as follows (link):
1 2 3 4 

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:
1 2 

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:
1 2 3 

 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:
1 2 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 linebyline implementation:
1 2 

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
.
1


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.
1


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


(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:
1 2 3 4 5 

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 rewritten as:
1


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.