In my previous blog post about categories and functors I already threatened you with the possibility of a followup. Well, here we go. Actually I won’t talk about category theory this time, but about an important abstraction from the world of advanced (at least in my understanding) functional programming which turns out to be a close relative to the now well understood functor.
Functors
Just in case you don’t remember exactly what a functor looks like, here comes the definition:
1 2 3 4 5 6 7 8 9 10 

For the sake of simplicity we will stick to the more specific Functor
definition throughout the rest of this post. Such a functor is an endofunctor, because its source and target are the same (the category of Scala types and Scala functions). Maybe you remember that such a functor can be regarded as a provider of a computational context: The function f: A => B
you give to fmap
is lifted into the functor’s context which means that it is executed (maybe once, maybe several times or maybe even not at all) under the control of the functor.
As an example let’s look at how the OptionFunctor
defined in the previous blog post is working: If we give fmap
a Some
the given function will be invoked, if we give it a None
it won’t be invoked.
1 2 3 4 5 6 

So far, so good. Using functors we can lift functions of arity1 into a computational context.
Applicatives
But what if we have a function of higher arity? Can we still use a functor to lift a function of, let’s say, arity2?
Let’s look at what happens if we call fmap
to partially apply an arity2 function to its first argument within the computational context of an Option
:
1 2 3 4 5 

What we get back is an Option[Int => Int]
, i.e. the “rest” of the partially applied function wrapped in an Option
. Now we have a problem, because we cannot give this lifted function to another call of fmap
.
1 2 3 4 5 

Of course we cannot, because fmap
expects a pure function, not a lifted one. And that’s the moment when applicatives enter the stage. The idea is simple and follows intutively from what we have just seen: Instead of fmap
taking a pure function, an Applicative
defines the method apply
taking a lifted function. And it defines the method pure
to lift pure functions. Using these it is perfectly possible to partially apply an arityn function to all of its arguments within a computational context. Before we look at our example from this new perspective, let’s code up our new abstraction:
1 2 3 4 5 6 7 8 9 10 11 12 

As you can see, there is a strong relation between functors and applicatives: Each applicative is a functor and by one of the laws for applicatives the following has to hold true: fmap = apply ο pure
. Well, this law is pretty intuitive, because it makes sure we can use an applicative as a functor, i.e. for a pure arity1 function, and it will behave as expected.
In order to be able to work with applicatives we need some scaffolding and for our Option
example we need an implementation of Applicative
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 

Now let’s look at our example from above with an arity2 function. Using a functor we got stuck, but using an applicative we can make it all the way through the two arguments:
1 2 

Ain’t that nice? You might answer, that this code looks cumbersome and is not necessary at all. Regarding the first argument please read on. Regarding the second: Yes, of course we don’t need an applicative for Option
, because it already offers flatMap
which does the job. But with this type class approach we can deal with any class, e.g. with Either
from the Scala standard library or with classes from our own projects.
Conclusion
You have seen the basic principle of applicatives: We can apply functions of arbitrary arity (well, greater or even one) to its arguments within a computational context. As functors provide exactly this for arity1, applicatives are generalized functors.
Thanks to Scala’s flexibility we can of course do much better than above. Using a little pimp my library and some operators we can get something that’s elegant and useful. Luckily the scalaz folks have already done this, so I will just show two ways of expressing the above example using this awesome library:
1 2 3 4 5 

Now this is really nice, isn’t it? Especially the second one looks very concise and intuitive. And as I stated above we can use it for types that don’t bring helpful methods like flatMap
. Let’s conclude with another example using Either
which is a perfect candidate to be used for results that might fail with well defined errors:
1 2 3 4 5 
