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

Popular Posts