The Point Of Applicative Functors
A few weeks ago, we talked about Functors. In case you’ve forgotten, you might want to click that link and read the post, but long story short: Functors allow you to call functions that take “bare” values on “dressed up” values.
Functors had one big weakness though: you can only call a function that takes one argument. I justified this using function currying, but there is another way. Today, we’ll be talking about that other way: Applicative functors.
Functors use a function called
fmap to call a function on a functor. This function is called on the value within the functor, and a new functor containing the result is returned. This looks like this:
Prelude> fmap show $ Just 1 Just "1"
Just 1, which returns
Just "1". Let’s try another one:
Prelude> fmap (+ 1) $ Just 1 Just 2
Here, we’ve used function currying to
fmap (+ 2) over
Just 1, which returns
Just 2. This is all fine and good in a contrived situation such as adding 1 to 1, but what about the real world? As you probably know, you often don’t have some nice constants ready to use in your function. There must be a better way…
Applicatives To The Rescue
To put it in layman’s terms, Applicative Functors are the middle ground. Let’s take a look at another example:
Prelude> let example = fmap (+) $ Just 1 Prelude> :t example example :: Maybe (Integer -> Integer)
fmap (+) over
Just 1, the result is a function that takes an Integer and returns an Integer, wrapped in a
Maybe. To do anything useful with this, you need to find a way to call this function on something else. That’s what Applicative Functors do for us. Let’s take a look at part of the class definition of Applicative:
class (Functor f) => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
We have two functions here:
pure, which takes a value and wraps it in a minimal Functor, and
(<*>), which takes a functor that contains a function, and calls it on another functor which contains a value, returning a value. In a nutshell, this allows us to call multi-argument functions with functors. There’s one more function exported by
Control.Applicative that bears mentioning:
(<$>) is basically an alias for
fmap that allows us to use
fmap as infix:
Prelude> fmap (+1) $ Just 2 Just 3 Prelude> (+1) <$> Just 2 Just 3
As you can see, they function identically. Feel free to use
(<$>), just know that it requires you to
import Control.Applicative. Anyways, with that out of the way, here’s how you use an applicative. For this example, I will be adding 2 to 2. But 2 will be wrapped in a
Maybe requiring extra steps:
Prelude> (+) <$> Just 2 <*> pure 2 Just 4
Let’s talk about what just happened. First, I
Just 2 using the
(<$>) operator. This returns a function that takes an Integer and returns an integer wrapped in a
Maybe. Next I used the
(<*>) operator to call that function on
pure 2, which returns
You may be wondering why I used
pure 2 instead of
Just 2. Sure,
Just 2 would have worked, but
pure 2 would have worked for any type of Applicative. Let’s take a look at another example:
Prelude> (+) <$> [1,2,99,100] <*> pure 2 [3,4,101,102]
As you can see, this is the exact same expression, except I used a List instead of a Maybe. This is where
pure comes in handy. It allows you to write more generic code.
pure works for any type of Applicative, therefore you needn’t concern yourself with what kind of Applicative is being passed into your function. You can rest easy knowing that it will work regardless.
Hopefully That All Made Sense
Applicatives are a pretty abstract concept, and it can be difficult to visualize how they can be useful. This is a topic that I don’t feel is documented very well. It’s a topic I had trouble with for a while.
Since I suspect that most readers will find this post with a google search term like “what is the point of applicative functors”, hopefully I’ve shed some light on the topic for you.