The Monad Module II : Applicative
The next data type we come to is called Applicative
. It’s not actually part of the Monad
module, but all instances of Monad
must first be an instance of Applicative
. And so in order to understand Monad
we need to understand Applicative
.
Applicative
is basically an extended form of Functor
. All instances of Applicative
are instances of Functor
. What Applicative
adds to Functor
is, essentially, function application.
You’ll remember from “The Monad Module I” that functors are a way of processing a value, where that value is nested within a structure (e.g. a list), without losing that structure. fmap
is the function that defines that process for any given Functor
datatype.
The problem with fmap :: (a -> b) -> f a -> f b
is that (a -> b)
only accepts a single parameter. What if you want to add two integers together with (+) :: Int -> Int -> Int
, where those integers are wrapped in a Just
, for the sake of argument, like so:
Just 4 + Just 5 = Just 9
The problem is that (+)
takes two arguments, but the first argument of fmap
only takes one argument.
So the first thing we’d have to do is to apply (+)
to Just 4
. Except, we can’t because the definition of (+)
is (+) :: Num a => a -> a -> a
.
We could unwrap the 4
from Just 4
using a case
statement or fromJust
, apply (+)
to the result, and then use that as follows:
fmap ((+) $ fromJust (Just 4)) (Just 3)
This will work, but it’s error prone, clumsy, and generally horrible.
Let’s look at how Applicative
comes to save the day:
Applicative
has the class definition:
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
The first function pure
takes a value of type a
and returns a value of type f a
, in other words it lifts
a value and places it in the structure of f
. For example, take the following snippet:
> pure 5 :: Maybe Integer
Just 5
So, once more considering values in terms of “structure”, pure
takes a value and puts it into the structure of that functor.
For instance, pure (+)
in the context of Maybe Integer
has the following signature:
Maybe (Integer -> Integer -> Integer)
So now, we have a function in the context of Maybe Int
. The next challenge is to apply values to that function which preserve the Maybe
structure.
That is where <*>
comes into play. It has the signature:
(<*>) :: f (a -> b) -> f a -> f b
So now we can do this:
> pure (+) <*> Just 4 <*> Just 5
Just 9
So Applicative
allows you to apply sequentially apply values within a functor.
There are several useful utility functions which come with Applicative
, notably <$>
. <$>
is an infix alias for fmap
. It means you can do the following:
> (+) <$> Just 5 <*> Just 4
Just 9
Which is also the same as:
> fmap (+) (Just 5) <*> Just 4
Just 9
The <$>
operator is the same as the $
operator, except it maintains the structure of a functor.
Written with StackEdit.
Comments
Post a Comment