Grasping Haskell: functors, applicatives and monads (part 2)
This is the second part in a series, the previous part covered Functors.
I like to introduce Functor Applicatives by introducing a problem.
Let’s say we have a function which takes two arguments and we want to apply it to two boxed values:
Easy right? We use a Functor! There’s only one problem: fmap only applies one value to a function.
How about applying fmap twice? We can use partial functions in Haskell!
first = fmap (+) (Just 3)
second = fmap first (Just 4)
Sadly, this won’t compile. In fact, it’s pretty obvious why: fmap returns the result (which is a partial function in this case) in a box. When we apply fmap the second time, it doesn’t get a (partial) function as first argument, instead it receives a boxed function.
Applicatives to the rescue!
The Applicative typeclass is located in the Control.Applicative module and defines two methods: pure and <*>.
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Again, we can derive a lot from this class definition:
- If we want to make a type an Applicative, it has to be a Functor first.
- pure takes a value and returns an Applicative with that value inside it.
- If you look closely, you’ll see that <*> resembles fmap pretty well. In fact, the only difference lies in the first argument! While fmap takes a function, <*> takes a function that is inside an Applicative.
That last sentence rings a bell, right? It should, because it solves the problem we encountered in the introduction.
Maybe as an Applicative
So how does this work in practice? Let’s have a look at Maybe’s Applicative implementation:
instance Applicative Maybe where
pure = Just
Nothing <*> _ = Nothing
(Just func) <*> something = fmap func something
Let’s break it down.
pure is simply Just. When implementing pure, you should aim to put the value in the minimal context that still yields that value. We’ll encounter a clearer example a bit further in this article.
Notice that <*> is an infix operator.
Extracting a function from Nothing is impossible, so we simply return Nothing again. However, if we match (Just func), we can apply fmap on the extracted func and something. This is possible because something also has to be a Functor!
Let’s apply our newly gained knowledge to our previous problem:
pure (+) <*> Just 3 <*> Just 4
equals Just 7.
But we’re not done yet! The Control.Applicative module exports another convenient function: <$>, which is defined as:
f <$> x = fmap f x
Recall that an Applicative is also a Functor, so we can use fmap. This allows us to rewrite our previous solution in a more elegant way:
(+) <$> Just 3 <*> Just 4
Beautiful, isn’t it?
List is an Applicative too!
As you could’ve guessed, lists also instantiate the Applicative typeclass. Let’s dive right into the implementation:
instance Applicative  where
pure x = [x]
fs <*> xs = [f x | f <- fs, x <- xs]
Recall that pure puts the value in the minimal context that still yields the same value. In this case, the least amount of context is achieved by putting it in a singleton list.
The implementation of <*> is worth a closer look. It’s nothing more than a list comprehension! It basically boils down to this: every function is applied to every value, and the results are returned in a new list. An example will make this more clear:
(*) <$> [2, 3] <*> [4, 5]
equals [8, 10, 12, 15].
I can hear you saying it already: that’s not what I always want! What if I want to apply each function in my first list to the respective value in the second list? The ZipList typeclass was made for this exact purpose, but for the sake of keeping things brief, I will not explain it here.
You should now have a firm grasp of the Functor and Applicative typeclass, and how they are related. If you have any questions or suggestions, do not hesitate to contact me!
The next and final article in this mini-series will handle Monads. Trust me, it’s not rocket science! Stay tuned.