This series is focused on one important typeclass – Applicative
– and what applicative functors are. The series starts from a motivating example, a somewhat stripped-down version of something you might have encountered in “real code” and shows how the problem is solved with “fmap
plus a little something extra.” We go on to explore applicative functors in depth, working up to showing how the Applicative
class provides us with some useful tools for concurrency and parsing, among other tasks.
Prerequisites:
- knowledge of basic types and also kinds;
- some comprehension of typeclasses and their relationship with types; and
- facility with Haskell syntax;
- a good understanding of
Functor
andfmap
.
History
Introduction
Applicative
, Foldable
, and Traversable
appeared in GHC 6.6 in 2006. The inclusion of applicative functors in GHC was preceded by two important papers: Applicative programming with effects introducing the idea, and The essence of the iterator pattern elaborating on its significance.
Inclusion in Prelude
For nearly a decade, these classes lived in their own separate modules: Control.Applicative
, Data.Foldable
, and Data.Traversable
. Then in 2015, things took a dramatic turn when GHC 7.10.1 brought them all into the Prelude
module. The types of many Prelude
functions were changed to make use of these constraints. For example:
length :: [a] -> Int -- in GHC 7.8
length :: Foldable t => t a -> Int -- in GHC 7.10
This version also formalized the relationship between Applicative
and Monad
by introducing a new constraint: Every Monad
instance is now required to have a corresponding Applicative
instance.
class Monad m where -- in GHC 7.8
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
class Applicative m => Monad m where -- in GHC 7.10
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
Lesson descriptions
Maybe
there’s a function – First we introduce a you might find in real everyday code: sometimes the function is embedded within aMaybe
context because it might not be there. This is a problemfmap
alone can’t solve for us. We need anfmap
for times when our function is stuck inside a constructor. But every problem is an opportunity to invent a solution, so we invent one. Then meet the typeclass that is designed for handling that situation in a general way,Applicative
.Applicatives are monoidal! – This lesson starts with a comparison of different
Applicative
idioms and comparesliftA2
directly with(<*>)
. Then we look at product types as applicative functors and the connection between monoids and applicatives.Sequencing effects – Here we take up with the rest of the
Applicative
class, namely the so-called sequencing operators,(*>)
and(<*)
. This leads us to consider carefully what is meant by effects when we talk about applicatives and gives us a new perspective on the connection, discussed in the previous lesson, of the connection between monoids and applicatives. We also give an example of applicative parsing techniques.List and
ZipList
– Now you’re in for a surprise: lists are applicative functors in two ways. In this lesson we talk about why – which does lead us to talking about monoids again, just when you thought we were done with that!The
Reader
context – This lesson revolves around a fun example: recruiter emails! As we did in theFunctor
series, we look at the function instance ofApplicative
as well as anewtype
wrapper for functions calledReader
and see how the applicative can make our tedious email chores more exciting.Applicatives compose – The
Reader
newtype is often combined withIO
in Haskell programs. Here we look atIO
as anApplicative
, what it means to say that functors compose, and how to combineReader
withIO
to make a single powerful applicative functor.The
Compose
newtype – In this lesson, we continue working with the notion of applicative functor composition. Our goal here is to make it even more abstract and generalizable, and we introduce theCompose
newtype
as one means of abstracting out the very notion of functor composition. We end the lesson with a look at a practical use ofDerivingVia
.An applicative Map – The way we have been discussing the
Applicative
class as an extension of the basic idea ofFunctor
might have led you to believe that allFunctor
s areApplicative
s but this is not the case. Here we’ll look at one important type,Map
, that is not, why it isn’t, and how we could make a “mappy” type that isApplicative
.Property testing – We write property tests for the
Applicative
laws. The peculiarities of these properties bring up several interesting new Haskell features, includingRankNTypes
andAllowAmbiguousTypes
. The next two lessons make use of the tests we write here.Homomorphism and composition – We begin a thorough dive into what the Applicative laws mean and what useful properties they assure. This lesson introduces the algebraic concept of homomorphism and covers two of the four laws: homomorphism and composition.
Identity and interchange – The Applicative identity and interchange laws are two halves of the a single idea:
(pure _ <*>)
and(<*> pure _)
are both fmap operations that preserve the Applicative structure of whatever value they are applied to. These limitations imposed upon “pure” values assure us that Applicative functors conceal no surprises.