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
Nothingresults inNothing. - Line 3: From the definition of the
Functortype, we know that the type offuncisa -> bandJust xcorresponds tof a. Applying the function toxinside the box results in a value of typebin 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:
fmust also be a functor (i.e. be an instance of theFunctortype class).- The
purefunction takes an arbitrary typeaand brings it into the functor. i.e.pureputsain a box of typef. <*>takes a functor (box) of typefthat contains a function of typea -> b, and a functor (box) of typefthat contains typea. It results in a functor (box) of typefthat 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.