This holiday weekend has (finally) enlightened me on some important topics in functional programming. It has persuaded me to take a real look at category theory.
It all began with an intuition of Applicative Functors. They really are such exquisite things.
I am by no means authoritative on the topic, my study has inspired me to try and write about it in a way that is hopefully understandable.
Alright, prologue aside, let's dive in. First off, let's quickly revisit
Functor, HKTs, their implications and how they're important. The language I will be using is Haskell (for now), for obvious reasons, but will try to explain it well.
Behind free monads, applicatives may be #1 pattern in FP IMO
— close friend
Revisiting Functors ↔
A Functor is defined over types of kind
* -> *, or of unary type constructors. You've probably come across them quite a bit, as they include
Maybe. Where types of kind
* are what we call concrete types (like
As an informal side note. In other languages they are quite literally similar to generic types such as
Option<f64>, for example, in Rust. What we really want is higher-kinded polymorphism.
Let's take a look at the Functor typeclass (I'll explain)
class Functor f where fmap :: (a -> b) -> f a -> f b
If you're unfamiliar with haskell syntax, that's okay. This essentially means for all instances of
Functor it must implement a function
fmap that takes a function of type
(a -> b) and a
f a (where
f is the type constructor or 'context': that is a type of kind
* -> * such as
List) and returns a
The best way to understand this concretely for someone just starting is to take a look at
map? Its signature looks like:
map :: (a -> b) -> [a] -> [b]
Did something just click there?!? What about the
Functor instance for
instance Functor  where fmap = map
I'm sure most of you have mapped over lists. But for completeness, An example is like
map (*2) [1..3]which gives us
That's right! Indeed
List is a
Functor, but there are in fact many types which are
Functor! We won't go over that here. The point is functors are things that can be mapped over.
Again, for completeness, Functors (Applicatives, and Monads) implementations must obey laws. I will omit proofs or anything rigorous here but be weary the instances should abide by the laws.
The Applicative Meat 🍖
Time for the juicy insides. Let's take a look at the definition:
class (Functor f) => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
Functor recap, some things here should be immediately clear. Also, for one,
Applicatives are indeed themselves also
Functors. Cool. So we see
Applicatives implement a
pure function and a
(<*>) (an infix function here, it's also called apply in other languages).
Is it obvious what
pure does here? It should be! if
f is some functorial context, then
a -> f a lifts
a into the Applicative Functor.
(<*>)? It looks quite similar to our
fmap above, doesn't it? Except we notice the
f (a -> b). Hmm. So it takes a
Functor that has a function in it and applies that function to a second
Functor. If you 'erase' the
f from the type signature, you get normal function application. Let's take a look at examples.
Holy Moly! See what's happening here? For one, In the first example
[ (2 *) ] is already a function in a
List is also an Applicative Functor!). So it's applying the
2 * function into
In the second example, we lift the binary
(*) function into the applicative context and then apply it to our applicatives.
So what's the big deal? 🤔
First off, suppose
<$> is the infix version of
fmap. What if we wanted to map a function over a collection of Applicative Functors?
(*)is a binary infix function that takes two parameters a la
2 * 2.
Wait one second here... You're telling me I can take a function and
fmap it over a collection of Applicative Functors? Inside their context?
Note that we only use
(*)here which takes 2 parameters. The real magic here is that we could use
n-aryfunctions and apply it over
To understand how powerful this is. Think about
Maybe and other very useful types. Suppose you have defined a
Middleware over a collection of routes that is an
Applicative Functor. How incredible and effective it would be to
fmap a function over and determine or compose independent effects for all routes.
When I realized how powerful this truly is, I was moved to order a category theory book straight away. Monads do have their shine, and for good reason! But to touch briefly on the difference, they are dependent on the value and context
a -> f b. Applicatives are less restrictive, and have many uses within all functorial types
Until next time.