Applicative Functors

In part 1 of this series, we discussed functors. A functor is a simple abstraction describing container types. Now the subject of this series is Monads, but we're still not ready for those just yet! First we're going to study a structure in between functors and monads.

This structure is the Applicative Functor. For simplicity, I'll often refer to these simply as "Applicatives". These are a little tricker than functors, but still simpler than monads. In this article we'll see how they work! (If you already know how applicative functors work, you can move on to part 3 where we finally talk about monads!)

What is an Applicative Functor?

An applicative functor is an abstract mathematical structure that defines a default or "base" construction for an object and allows function application to be chained across multiple instances of the structure. All applicative functors are functors, meaning we must be able to "map" a transformation function over them as well.

How are Applicatives Represented in Haskell?

Just like normal functors, applicatives are represented using a typeclass, simply called Applicative.

class (Functor f) => Applicative f where
  ...

As stated above, all applicative functors must naturally be functors, so we see a constraint on the Functor class here.. This means we can assume that fmap already exists for all these types.

Most often, the two functions we use to describe an Applicative instance are pure and (<*>), also known as the "apply" operator.

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

The pure function tells us how we can wrap a normal object into an instance of this structure. This is the "default" mechanism mentioned above.

The "apply" operator fulfills the second part of the description. It allows us to chain operations by wrapping a function in our structure. The name "applicative" comes from the fact that we "apply" functions from within the structure, rather than simply from outside the structure, as was the case with normal functors.

Basic Applicative Examples

Many of our basic functors also have instances of Applicative. For example, here's the instance for Maybe:

instance Applicative Maybe where
  pure = Just
  (<*>) Nothing _ = Nothing
  (<*>) _ Nothing = Nothing
  (<*>) (Just f) (Just x) = Just (f x)

We can "lift" any normal value to become a Maybe value using Just, so this is the implementation for pure. Then with function application, if either side is Nothing, our result is Nothing, and if we have a function AND a value, then we apply the function to the value and wrap it with Just.

Lists also have an Applicative instance, though it's not necessarily the most intuitive:

instance Applicative [] where
  pure a = [a]
  fs <*> xs = [f x | f <- fs, x <- xs]

The pure instance is simple. We wrap our item as a singleton list. But then what happens with the "apply" operator? We have a list of functions and a list of items, like so:

>> [(+2), (*3)] <*> [4, 6]

When I first looked at this, my intuition suggested we would apply each function to the corresponding value. This would result in [6, 18] in the example above. But this isn't what we get!

>> [(+2), (*3)] <*> [4, 6]
[6, 8, 12, 18]

Instead, the instance applies every function to every value in a pairwise manner.

This makes it really convenient to solve certain problems. For example, how do we get the pairwise sums of elements in two lists? There are actually two ways to do this:

>> pure (+) <*> [1, 2, 3] <*> [4, 5, 6]
[5, 6, 7, 6, 7, 8, 7, 8, 9]
>> (+) <$> [1, 2, 3] <*> [4, 5, 6]
[5, 6, 7, 6, 7, 8, 7, 8, 9]

In the first way, we start off by wrapping our function in an applicative with pure. Then we apply this to the first list, followed by the second list in a chain of operations.

In the second way, we observe that "applying" a singleton list is the same as fmap. So we use <$> to "transform" each element of the first list into a function, and then apply these functions over our second list.

If we had 3 lists, and wanted to sum the combinations of numbers from each list, we could do this as well. First we would define a function taking three inputs and summing them, and then we could chain apply this over the three lists.

>> let f a b c = a + b + c
>> pure f <*> [1, 2, 3] <*> [10, 15] <*> [100, 200]
[111, 211, 116, 216, 112, 212, 117, 217, 113, 213, 118, 218]

The more "intuitive" (at least for me) approach to combining lists is captured by the ZipList type.

>> import Control.Applicative
>> ZipList [(1+), (5*), (10*)] <*> [5,10,15]
ZipList {getZipList = [6,50,150]}

In this way, each function in the first list matches with one element in the second list!

How are Applicatives different from Functors?

Applicatives let us combine wrapped data in a way that is difficult with normal functors. Consider this example, where we would like to "multiply" two Maybe values.

>> (Just 4) * (Just 5)
>> Nothing * (Just 2)

Can functors help us here? We can use fmap to wrap multiplication by the particular wrapped Maybe value:

>> let f = (*) <$> (Just 4)
>> :t f
f :: Num a => Maybe (a -> a)
>> (*) <$> Nothing
Nothing

This gives us a partial function wrapped in a Maybe. But we still cannot unwrap this and apply it to (Just 5) in a generic fashion. So we have to resort to code specific to the Maybe type:

funcMaybe :: Maybe (a -> b) -> Maybe a -> Maybe b
funcMaybe Nothing _ = Nothing
funcMaybe (Just f) val = f <$> val

The code we write here obviously doesn't generalize to other Functor types.

Instead, we can solve this problem very neatly using the Applicative instance:

>> pure (*) <*> Just 4 <*> Just 5
Just 20
>> (*) <$> Nothing <*> Just 2
Nothing

Example: A Functor that is not Applicative

From part 1, we had this example of a Measurements type that had a Functor instance:

data Measurements a = Measurements
  { totalSize :: a
  , numBedrooms :: Int
  , masterBedroomSize :: a
  , livingRoomSize :: a
  } deriving (Show, Eq)

instance Functor Measurements where
  fmap convertMeasure (Measurements ts nb mbs lrs) = Measurements
    (convertMeasure ts) nb (convertMeasure mbs) (convertMeasure lrs)

It wouldn't really make sense to make an Applicative instance here. How would you write pure in the Applicative instance? By taking a single Int value and plugging it in for total size AND the master bedroom size AND the living room size? That wouldn't really make sense. And what would the numBedrooms value be for the default? What would it mean to "chain" two of these objects together?

We can't answer these questions very well, which suggests this type isn't really an Applicative functor.

What are the Applicative laws?

While functors had two laws, applicatives have four laws. At first glance, these aren't necessarily very intuitive.

-- Identity
pure id <*> v = v

-- Homomorphism
pure f <*> pure x = pure (f x)

-- Interchange
u <*> pure y = pure ($ y) <*> u

-- Composition
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)

Ultimately though, these rules encapsulate the same ideas as the functor laws. I would summarize them with three statements.

  1. Applying pure should not change the underlying values or functions
  2. Applying the identity function through an applicative structure should not change the underlying values or structure.
  3. It should not matter what order we group operations in.

This third idea in particular is useful because it means that we can apply parallelism to a long chain of applicative operations. This also comes down to the fact that applicative functors are context-free, an idea we'll discuss more with monads.

How do Applicatives help with Monads?

Applicatives are helpful for the same reasons as functors. They're a relatively simple abstract structure that has practical applications in our code. Now that we understand how chaining operations can fit into a structure definition, we're in a good position to start thinking about monads!

Conclusion

In the next part of this series, we'll finally get to monads! So head over there if you've been dying to understand this critical Haskell concept. Then you can get more knowledge by looking at different examples of monads in the rest of the series.