In my last Haskell Article I discussed functors, which provide a generic way to map functions over values. In this article I'm exploring applicatives, which build on top of functors. After discussing applicatives in Haskell, I'll try implementing them in Java.
As I mentioned, applicatives take the concepts of functors and builds upon them. Applicatives are so closely related to functors that they are often referred to as applicative functors1. Functors provide the ability to map a function across every value in a type. Remember that the functor type class defines a single function
fmap like so:
fmap takes a function of type
(a -> b) and applies it to a value of type
f a, resulting in a value of type
f b. While
fmap has many use cases, it also has a limitation - its first argument is a function that can only have a single argument of its own (
a). Applicatives alleviate this problem.
An applicative (also known as an applicative functor) allows a function that takes any number of arguments to be mapped over the values in a type. Applicatives in Haskell are defined by an
Applicative type class, which extends the
Functor type class.
Applicative type class defines at least two functions -
pure takes a value of any type and transforms it into a value whose type is an
Applicative type is a container holding the original value. For example, when making
Maybe an instance of
pure is defined as
pure x = Just x. In this context
pure wraps any value inside a
Just container value.
(<*>) (pronounced "ap" or "apply"3) is a generalized function application where the function and arguments are functors. Function application is simply applying a function to an argument, so
(<*>) applies the function
f (a -> b) to the argument
With these two building blocks, applicative functors are created which allow a function of variable argument length to be mapped over the values in a type. The following code defines the
Maybe type as an instance of
pure wraps a value in the
Just constructor function.
Just is a constructor function for the
Maybe type, which is an instance of
Just represents success in the
Maybe type while
Nothing represents failure.
<*> (apply) maps a
Maybe value to a
Maybe function. If the function is equal to
Nothing, the apply function propagates the failure by returning
Nothing. If the function is wrapped inside
fmap is called with the function and value as arguments. Here are some basic examples that utilize the
(<*>) functions with the
Another cool implementation of the
Applicative type class involves lists.
Applicatives are an enhancement on top of functors. While
fmap functions can perform the same tasks as applicatives, their implementation is cumbersome and doesn't scale4.
The two functions defined in the
Applicative type class can be used to create
fmap functions with any number of arguments. For example, I created the following functor type classes where
fmap maps a function with zero, one, and two arguments to every value in a type:
I made the
Maybe type an instance of
fmap0 is functionally equivalent to
pure, wrapping a value in a functor type.
fmap1 is equivalent to the
fmap function in the
Functor type class.
fmap2 takes two values and applies them to a function which takes two arguments. The functionality of these three functions can be seen in the following examples:
Instead of defining functor type classes for every number of arguments, we can simply chain any number of
(<*>) functions as seen in
fmap2. For this reason, the
Applicative type class is used instead of different versions of the
Unlike Functors, Applicatives can't be expressed in Java5. This is because Java types lack Higher-Kinded polymorphism while Haskell types have Higher-Kinded polymorphism. In Haskell terms, Higher-Kinded types apply to type constructors6.
A Kind is the type of a type constructor7. Haskell is a language whose type system is made up of kinds. The most basic kind is denoted as
*. This basic kind is called “type”8. A more complex kind that represents a type constructor with a single argument is represented as
* -> *. For example,
data Maybe a = Nothing | Just a is a single argument type constructor.
A Higher-Kinded Type is a type that takes another type as an argument and constructs a new type9. In the same way a higher-order function takes functions as arguments and returns a function, higher-kinded types take type constructors as arguments and return a new type10. An example of a higher-kinded type is
(* -> *) -> * -> *. In Haskell this could be represented as
data HKT f a = HKT (f a)11.
If you want to check out the kinds of type constructors in Haskell you can use the
:k command in GHCI.
In my research I've seen evidence that Applicatives can be expressed in C#. Perhaps that will be the topic of a future article.
Applicatives are a complex topic in functional programming and I'm still trying to wrap my brain around all their intricacies. If you are reading this article to assist your own understanding of applicatives, I recommend reading many different articles on the topic along with writing code samples yourself. Each article will add a piece to the puzzle in understanding applicatives. In my next Haskell article I'll discuss monads! All the code from this article is available on GitHub.