Applicative

The next generally useful type class we'll be looking at is Applicative. Applicative is built on top of Functor. For that reason, the Applicative instances can only be defined for types that have a Functor instance.

Let's take a look at the definition of Applicative.

class Functor f => Applicative f where
  pure  :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

First, it provides a function called pure which given any a, outputs an f a. So all pure does is provide a way to take some simple value and embed it into our structure. We'll see what that means in a moment.

The two angle brackets with the asterisk in the middle is pronounced "apply" or "app" and sometimes by other names.

What apply does is take some function that is embedded in a structure and apply to a value inside of some structure.

Let's go back to Functor for a minute. Functor let us take ordinary functions and sort of promote it to work within some context or structure. As an example, let's specialize the map operation from Functor to the Optional type. It looks like this:

map :: (a -> b) -> Optional a -> Optional b

map takes a plain function from a to b and an Optional a and applies the function within that context, outputting an Optional b. We can state this in a much clearer and concise way by adding some parentheses like this:

map :: (a -> b) -> (Optional a -> Optional b)

You should remember from our discussion on currying and how every function really outputs another function, that these additional parentheses make no difference - the function is exactly the same with or without them. map really takes a function from a to b and outputs a function from Optional a to Optional b. Now we can see it more clearly, what Functor provides is a way to promote an ordinary function to a function that works within some computational context. The Optional type is a computational context. We can use map to take a function from a to b and turn that into a function from an Optional a to Optional b. The same applies for any f where f is some type of structure.

Ok, so far so good. Functor lets us apply a function within some context. But what about applying a function which itself exists within some structure to a value inside some structure. In other words, what if we have Optional (a -> b) and Optional a? How can apply the function inside of this Optional context to the value in the other Optional? Functor will not help us with this.

Functor gives us:

map :: (a -> b) -> f a -> f b

but what we're looking for is

apply :: f (a -> b) -> f a -> f b

We can line these up to see clearly how Functor and Applicative relate to one another. Let's remove the name and just line up the types.

(a -> b) -> f a -> f b

f (a -> b) -> f a -> f b

They are exactly the same except that in the case of Applicative the function that we want to apply is itself embedded within some context. That's the f over here in the first argument.

Let's start with a dead simple type, we'll call it Id and we'll write both a Functor and Applicative instance for it.

data Id a = Id a deriving Show

This is not a very useful type, but for our examples all we want is a type that just adds some context for a value. Instead of a plain a, we have an Id a.

The Functor instance for this is trivial. Let's write it:

instance Functor Id where 
  map f (Id a) = Id (f a)

All we have to do is apply the function to the a value and wrap the resulting value back up using the Id constructor.

And now we can use the Functor instance for Id the same way we use it for any other type.

> map (+ 1) (Id 1)
Id 2

What about the Applicative instance. Well Applicative requires us to define both the pure function and the apply function. Let's try pure first.

instance Applicative Id where 
  pure a = Id a 

Implementing pure is very simple. There's only one possible thing we could do which is to just output an Id value by shoving the a inside of one using the Id constructor.

Now let's implement apply:

Id f <*> Id a = Id (f a)

This implementation is also pretty easy in the case of Id. We have a function inside of the Id context and a value of type a in an Id context. So we just grab the function, apply it to the a and wrap it back up in Id.

Let's try it out:

> Id (+ 1) <*> Id 1
Id 2

Here we have a function that adds 1 to an Int. We use apply from Applicative to apply this function to the value 1 in the other Id.

Let's take a detour for a moment to help us develop a bit of a clearer understanding of Applicative.

We know that in Haskell, function application has higher precedence than any other operator. For that reason, the following won't work:

> let greeting = "hello"
> putStrLn greeting ++ " world"
<interactive>:6:1:
    Couldn't match expected type ‘[Char]’ with actual type ‘IO ()’
    In the first argument of ‘(++)’, namely ‘putStrLn greeting’
    In the expression: putStrLn greeting ++ " world"

We'd like to print "hello world" but this doesn't work because of the precedence of function application. The high precedence means that Haskell tries parsing what we wrote like this:

(putStrLn greeting) ++ " world"

That's exactly what the error said, even if it said it in a pretty confusing way. The error told is that the ++ operator expects a String, but in this case it found a type of IO () on the left side. That's because it parsed it the way we just showed.

How can we fix it. We can use parantheses as we've been doing from time to time.

> putStrLn (greeting ++ " world")
hello world

There's another way we can fix this which is by using an alternative function application operator. The operator is represented be a dollar sign and has the following type signature:

($) :: (a -> b) -> a -> b’

As you can see, this operator really doesn't do anything. It just takes a function from a to b and an a and outputs a b. But isn't that just function application which we can accomplish with simple whitespace as we've been doing all along? This is correct! But what this operator adds is the fact that it has the lowest precedence of any operator. The result is that when you have a dollar sign, it's like saying "please evaluate everything to the right of me first". Our "hello world" example is a good one to illustrate use of the dollar sign:

> putStrLn $ greeting ++ " world"
hello world

We use the dollar sign to force the concatenation of "hello" and "world" first. putStrLn is then applied to this entire evaluated expression instead of binding to "hello" first.

Remember this dollar sign operator because it is used frequently in Haskell code. Style and other considerations will determine when to use dollar signs versus parentheses. You'll get a better sense of it the more code you read and write.

Why did I bring up the dollar sign operator here? Because it's a very useful operator to compare with our apply operator from Applicative. Let's line them up and take a look.

  (a -> b) ->   a ->   b
f (a -> b) -> f a -> f b

Lining these operators up this way gives us a very easy way to think about Applicative. apply is exactly the same as the dollar sign operator, but with everything inside of an f context. What this means is that we can look as apply from Applicative as nothing more than function application within some computational context! So apply is precisely function application with the only difference being that everything in apply is wrapped in an f context.

Exercises

1

Implement the Applicative instance for the Optional type.

2

Implement the Applicative instance for our List type.

There are actually two different and completely legitimate ways we could implement the Applicative instance for List. Since the f in this case is List, what we'll have is:

List (a -> b) -> List a -> List b

Since our "context" is List we have many functions and many elements a to apply those functions to. As a result, we could define the <*> operator so that it applies each function to each element of the List. If we do that, it means the output will be a list whose length is the length of the list of functions multiplied by the list of as.

The other way we could define it is by pairing up the functions in the list and the elements a in the other list. The first function will be applied to the first element of the List a, the second function to the second element of List a and so on and so forth.

We can only have a single Applicative instance though so pick one and implement that. We will see later how to achieve having Applicative defined for List in two different ways.

Complete and Continue