Applicative Functor: The Power of Less

Tobias Wennergren
3 min readMar 29, 2018

--

Working with monads in Scala is common and many of us deal with them on a daily basis. Some of the more common ones are List, Option, IO, and Either. In Scala, there is also the for-comprehension syntax that makes writing and reading monadic code easier. Here below are two examples:

Example of using monads in every day coding

The monad is a powerful abstraction but we often forget that there are other, strictly less powerful, options out there. Choosing the least powerful abstraction is usually wisest, as these less-powerful options have interesting properties that often prove useful

One of these options is the applicative functor. Applicative functors allows both composition and parallel evaluation, which are properties that the monad does not have. All monads are also applicative functors and the cats library added (since 1.0) some new syntax to make them easier to work with (mapN(..) on tuples, for example; more on this later in the article)

The rule that I follow when choosing between using a monad or an applicative functor is to ask myself the question: Are my programs evaluation independent of each other? If true, then applicative functor is a good choice.

Example of dependent and independent programs:

Dependent vs independent programs

The typeclass for applicative functor usually looks like this:

Applicative Typeclass

The first thing we notice is that an applicative functor is also a covariant functor (most common functor). This means that we have access to the map function. It also has a generic constructor called pure that can be used to create any applicative.

The ap function is a tad more confusing and harder to get an intuitive feeling for. I often think of it in a partial applied form that returns the function F[A] F[B]. The ap function then becomes a function that takes a F[A B] (function inside the context) and returns a F[A] F[B] (function outside of the context). The structure F[A B], even though it looks odd, appears when we provide a function with arity larger than one for map function. This can be seen here:

It is, however, more common to use functions that we can derive from ap than the function itself. One such function is map2, which we can implement using ap and map:

Applicative functor typeclass

The map2 function is easier to work with then the ap function and fits many common use cases. We can think of it as: Given two programs, F[A] and F[B] and a function (A,B) Z, it will then return a new program F[Z].

We can now rewrite the independent portion of our initial example with the map2 function:

It works, but our toy example with Bob Axel is not a good real-world example of the applicative. A better, closer to real-world example, is the problem of calling multiple remote API’s [to gather data] and then using the result to create an object. Below is an example of this:

An example of loading data from multiple sources

We could then use the monad to build the user object:

Monad version to create the user object

Or we can use the less powerful applicative functor (with map2):

Applicative functor version to create the user object

A benefit of the applicative functor version is that the two programs (the API calls) can be executed in parallel. The monad version will first run the getName program and wait for it to complete and then run the getAddress program.

Another benefit of the applicative functor is that it composes.

To demonstrate this, let’s extend our example with the idea that our APIs optionally returns data. The condition is that they will only return data if the user exists. We will model this using Options.

Using monads for these sort of nested structures is a bit more complicated (nested for-comprehension or monad transforms). The reason for this is that monads do not compose, i.e we can’t (generally) combine two monads to create a third that works on nested structures (in this case the IO monad and the Option monad). The applicative functor does not have this shortcoming and composes freely. This means that we can write one compose function that composes any two arbitrary applicative functors.

Compose function for applicative functors

This compose function already exists in the cats library, and I would highly recommend spending some time understanding it. The type currying expression “Lambda[l F[G[l]]]” comes from the kind projection library

We can now use this composed applicative to solve our API example:

This works for any type depth, and the functions map[2..22] are defined in the cats library.

--

--