Functortown: Applicative

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 and fmap.

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 a Maybe context because it might not be there. This is a problem fmap alone can’t solve for us. We need an fmap 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 compares liftA2 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 the Functor series, we look at the function instance of Applicative as well as a newtype wrapper for functions called Reader and see how the applicative can make our tedious email chores more exciting.

  • Applicatives compose – The Reader newtype is often combined with IO in Haskell programs. Here we look at IO as an Applicative, what it means to say that functors compose, and how to combine Reader with IO 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 the Compose newtype as one means of abstracting out the very notion of functor composition. We end the lesson with a look at a practical use of DerivingVia.

  • An applicative Map – The way we have been discussing the Applicative class as an extension of the basic idea of Functor might have led you to believe that all Functors are Applicatives 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 is Applicative.

  • Property testing – We write property tests for the Applicative laws. The peculiarities of these properties bring up several interesting new Haskell features, including RankNTypes and AllowAmbiguousTypes. 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.

Join Type Classes for courses and projects to get you started and make you an expert in FP with Haskell.