A Deeper Look at Applicative

Constructors are Functions

Remember how constructors are really just functions? For example:

> data Person = Person String Int deriving Show
> :t Person
Person :: String -> Int -> Person

When we ask GHCi to tell us the type of any constructor, it informs us that the constructor is a function which takes however many arguments the constructor takes and outputs a value of whatever the type is.

Person is a type and it has a single constructor, also named Person. It takes two arguments, a String and an Int.

What is the type of Person? It is a function which takes a String and an Int and outputs a Person.

The same applies for any constructor.

> data Car = Car String String Int deriving Show
> :t Car
Car :: String -> String -> Int -> Car

The Car type has a single constructor which takes a String (for the make), a String (for the model) and an Int (for the year).

The type of Car is String -> String -> Int -> Car.

Ok, so constructors are functions. We mentioned in previous lessons that this fact would become useful to us later. Now that we've been introduced to the Applicative type class, we're in a good position to see one of the benefits of the fact that constructors are functions as well as a practical and common use case for Applicative.

To make a Person, I need a String and an Int but I don't have those!

Imagine a very simple scenario: you're writing a web application and need to process some input from the user that is submitted via a form. It is possible that some fields of our form have been left blank. As such, when we process the form in our application, we've decided to represent this possible failure using the Optional type. If the field was filled out, we get a Some and if it's empty we'll get a None.

If we want to construct a Person using text submitted via a form, this means we'll end up with Optional String and Optional Int. How are we supposed to create a Person from this? The Person constructor is a function, but it has this type signature:

String -> Int -> Person

That doesn't help us, since we don't have String and Int, but instead Optional String and Optional Int.

You should realize right away that there's simply no way to materialize a Person if all we have is an optional String and an optional Int. But we can definitely output an Optional Person!

In fact, we can write a function that does this without too much trouble. Let's try:

maybePerson :: Optional String -> Optional Int -> Optional Person
maybePerson name age = case name of 
  Some n -> case age of 
    Some a -> Some (Person n a)
    _ -> None 
  _ -> None 

Here we use a case expression to output Some Person if the name and age supplied are both Somes. If either of them is a None, we output a None.

We could also write it using pattern matching:

maybePerson :: Optional String -> Optional Int -> Optional Person
maybePerson (Some age) (Some name) = Some (Person age name)
maybePerson _ _ = None 

Does this work? Let's see:

> let name = Some "Bob"
> let age = Some 42
> maybePerson name age
Some (Person "Bob" 42)

And if we have None for one of the values

> let name = Some "Bob"
> let age = None
> maybePerson name age
None

or both of them

> let name = None
> let age = None
> maybePerson name age
None

We get None.

Has to be a better way

Great, so we know we can write a function that help us construct a Person -- or at least an Optional Person if instead of having a String and Int at our disposal, we only have an Optional String and Int.

But implementing this functionality the way we just did is tedious, error-prone and, most of all, verbose. Also, Optional isn't the only kind of container or context we might have and we don't want to have to write this type of function for each possible context!

Let's back up and see what it is we are really looking for.

As we've mentioned, ordinarily we're looking to construct a Person using a plain String and Int. In other words, the constructor function works just fine since it's signature is:

String -> Int -> Person

But we need something that looks like this:

Optional String -> Optional Int -> Optional Person

Well, let's express this situation as a function. We have String -> Int -> Person but we want Optional String -> Optional Int -> Optional Person. So we need a way to get what we want given what we have. In other words, we need a function that looks like this:

(String -> Int -> Person) -> (Optional String -> Optional Int -> Optional Person)

See why? The first argument is the signature for the Person constructor, which we have, but we need to add the Optional type constructor to each of those types.

Let's look at this even more generaly and forget about the particulars of Optional. We'll think just in terms of type constructors so we'll reframe the problem as this:

(String -> Int -> Person) -> (f String -> f Int -> f Person)

instead of Optional we now just have any type constructor f.

Let's generalize this even further!

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

There's no reason we need to think about the Person type specifically so we'll just think in terms of the type variables a, b and c.

The Shortcomings of Functor

Let's look again at the signature of the function we need:

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

Does this signature seem somewhat familiar? If it does, it's because it looks very similar to the signature of the map function from the Functor type class.

We want this:

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

And Functor's map function is:

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

The function we're looking for is nearly identical to map, except that the function we'd like has an extra argument. What can we do?

Well let's just try writing this function ourselves. We'll call it map2 since, unlike the regular map, the first argument here will be a function that takes two arguments instead of one ((a -> b -> c) instead of (a -> b)).

We're going to try to use the existing map function in implementing this. In order to do that, we need a constraint on this function which is that we'll require f to be a Functor. You should recall that this is how we do constrained polymorphism in Haskell.

Ok, so our type declaration is this:

map2 :: Functor f => (a -> b -> c) -> (f a -> f b -> f c)
map2 f fa fb = undefined -- what do we do here??

You can try implementing this function..it's actually a really good exercise. After some time, though, you'll conclude, rightfully so, that it just isn't possible to implement this with the power of Functor's map alone. It just doesn't work. Why exactly? Let's follow the types:

the f binding in our map2 function has the type a to b to c.

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

What happens if we use map with f? Here we just want to know what the type will be if call map with f and omit map's second argument. Remember, this is called partial application. There's nothing wrong with examining the type at each step in function application.

Well let's remember what map does. All it does is lift a function from being a function a -> b to a function f a -> f b. So here that means that means that map f has this type:

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

So far so good. The second argument for our map2 function is an f a so let's provide that as the second argument in our application of the map function. What do we get?

map f fa :: f (b -> c)

At this point we've "used" the f argument, we've used the f a argument. The result we're left with is that we still have f b to use somehow and the result of using map with f and f a is a type of f (b -> c).

To summarize, we're left with

Using map with our first two arguments gives us f (b -> c) and we still have that f b argument just dangling there.

You can try all you want, but map does not allow us to implement map2.

Applicative to the Rescue

So Functor fails us and we're back to square one. Our original goal was a pretty simple one. We wanted to construct a Person but we only had an Optional String and an Optional Int. We quickly realized that we needed was a function that could do this:

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

We tried using map but it didn't work because we were left with

f (b -> c) and an f b and there's nothing map can do to help us with that.

You may realize by now that Applicative is precisely what we need! Applicative provides the power that Functor fails to give us. Look at "apply" from Applicative. The whole point of it is to do exactly what we've been looking for.

(<*>) :: f (a -> b) -> f a -> f b

Given a function inside of a context and a value inside of some context, apply the function to the value.

So the dead-end we arrive at with Functor is exactly where Applicative picks up. Let's use this then to implement what we wanted!

First, we know that the constraint on our function needs to be Applicative now. We initially thought the only constraint we needed was Functor, but it's clear now that Functor doesn't provide enough power and we need to resort to Applicative to accomplish our goal.

So we'll first change the signature of map2 to reflect this:

map2 :: Applicative f => (a -> b -> c) -> (f a -> f b -> f c)

Now we can implement this:

map2 :: Functor f => (a -> b -> c) -> (f a -> f b -> f c)
map2 f fa fb = map f fa <*> fb 

Let's look at this implementation

map f fa <*> fb

The first part is map f fa. The type of this is f (b -> c). We can now use "apply" from Applicative to finish this off since "apply" says that if you have a function in a context, I can apply it for you to a value in a context. Well, we do have a value in a context, that's f b. That's all there is left to do, use "apply" with f b:

map f fa <*> fb

In other words, we have map f fa which reduces to

f (b -> c) and we have

f b.

"Apply" is precisely for this situation:

(<*>) :: f (a -> b) -> f a -> f b

Let's see what we can do with this now:

> map2 Person name age
Some (Person "Bob" 42)
> map2 Person name None
None
> map2 Person None (Some 42)
None

If we wanted to do something similar when constructing a Car, we'd need a map3 function, since the Car constructor takes three arguments. We can implement this using the same exact pattern we used to implement map2:

map3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d
map3 f fa fb fc = ((map f fa) <*> fb) <*> fc

Let's try it out:

> let make = Some "honda"; model = Some "accord"; year = Some 2016
> map3 Car make model year
Some (Car "honda" "accord" 2016)

These functions map2 and map3 that we've implemented are actually included in the standard library. In the Prelude, they are called liftA2 and liftA3 respectively.

Pretty it Up

There are cases where using liftA2 and liftA3 makes sense, but in most cases, using the pattern of combining map with <*> directly is better, such as when we did:

> map Person name <*> age
Some (Person "Bob" 42)

But there's a simple thing we can do to make this prettier and it's what is done in the Prelude. We can simply add an infix version of map from Functor.

(<$>) :: Functor f => (a -> b) -> f a  -> f b 
(<$>) = map

With that, we can now do:

> Car <$> make <*> model <*> year
Some (Car "honda" "accord" 2016)

Or:

> Person <$> name <*> age
Some (Person "Bob" 42)

Much nicer.

In this lesson we took a closer look at the motivations behind Applicative and a few cases that showcase the tremendous power and usefulness of this simple type class.

Complete and Continue