Chapter 17: Applicative

17.1 Applicative

Applicatives are monoidal functors. The applicative type class allows for function application lifted over structure (like functor). But with applicative the function we’re applying is also embedded in some structure.

In this chapter, we will:

  • define and explore the Applicative type class and its core operations;

  • demonstrate why applicatives are monoidal functors;

  • make the usual chitchat about laws and instances;

  • do a lot of lifting;

  • give you some Validation.

17.2 Defining Applicative

The first thing you’ll notice here is that the type argument to Applicative is also a functor.

·∾ :info Applicative

class Functor f => Applicative (f :: * -> *) where
  pure              ::             a -> f a
  (<*>)             ::    f (a -> b) -> f a -> f b
  GHC.Base.liftA2   :: (a -> b -> c) -> f a -> f b -> f c
  (*>)              ::                  f a -> f b -> f b
  (<*)              ::                  f a -> f b -> f a
  {-# MINIMAL pure, ((<*>) | liftA2) #-}

instance Applicative  (Either e)   -- Defined in Data.Either.
instance Applicative   []
instance Applicative   Maybe
instance Applicative   IO
instance Applicative  ((->) a)
instance Monoid a =>
         Applicative  ((,) a)

The two core operations of Applicative are pure and <*> (pronounced “apply”).

pure lifts some value into an applicative context.

The <*> operator is described as “sequential application” (or sometimes “apply” or just “ap”). It applies monoids contained in one functor to the contents of another functor (as seen above).

Along with these core functions, the Control.Applicative module provides some other convenient functions:: liftA, liftA2, and liftA3.

liftA :: Applicative f
      => (a -> b) -> f a -> f b

liftA2 :: Applicative f
       => (a -> b -> c) -> f a -> f b -> f b

liftA2 :: Applicative f
       => (a -> b -> c -> d) -> f a -> f b -> f b -> f d

17.3 Functor vs. Applicative

Let’s review the difference between fmap and <*>:

(<$>) :: Functor f      =>    (a -> b) -> f a -> f b
(<*>) :: Applicative f  =>  f (a -> b) -> f a -> f b

The difference is we now have an f in front of our function (a -> b). The increase in power it introduces is profound.

For one thing, any Applicative also has a Functor and not merely by definition – you can define a functor in terms of a provided Applicative instance.

fmap f x \(=\) pure f <*> x

Here’s an example:

·∾ import Control.Applicative

·∾ fmap (+1) [1,2,3]
[2,3,4]

·∾ pure (+1) <*> [1,2,3]
[2,3,4]

Keeping in mind that pure has type Applicative f => a -> f a, we can think of it as a means of embedding a value of any type in the structure we’re working with:

·∾ pure 1 :: [Int]
[1]
·∾ :type it
it :: [Int]

·∾ pure 1 :: Maybe Int
Just 1
·∾ :type it
it :: Maybe Int

·∾ pure 1 :: Either a Int
Right 1
·∾ :type it
it :: Either a Int

·∾ pure 1 :: ([a], Int)
([],1)
·∾ :type it
it :: ([a], Int)

The left type is handled differently from the right in the final two examples for the same reason as here:

·∾ fmap (+1) (4,5)
(4,6)

The left type is part of the structure, and the structure is not transformed by the function application.

17.4 Applicative functors are monoidal functors

First let us notice something:

($)   ::   (a -> b) ->   a ->   b
(<$>) ::   (a -> b) -> f a -> f b
(<*>) :: f (a -> b) -> f a -> f b

In the type signature above, mappend is our function from (a -> b). Each operator lifts a bit more. <$> lifts the output into a functorial context, and <*> additionally has its input function within a functor.

·∾ (Just (*2)) <*> (Just 2)
Just 4

·∾ (Just (*2)) <*> Nothing
Nothing

·∾ Nothing <*> (Just 2)
Nothing

·∾ Nothing <*> Nothing
Nothing

With Maybe, the ordinary functor is mapping over the possibility of a value’s nonexistence. With the Applicative, now the function also might not be provided.

17.4.1 Show me the monoids

Recall that the Functor instance for the two-tuple ignores the first value inside the tuple:

·∾ fmap (+1) ("blah",0)
("blah",1)

·∾ :info (,)
data (,) a b = (,) a b  -- Defined in ‘GHC.Tuple’
instance Monoid a => Applicative ((,) a) -- Defined in ‘GHC.Base’
instance (Monoid a, Monoid b) => Monoid (a, b) -- Defined in ‘GHC.Base’

Alright, that makes sense so far. But what about this?

·∾ ("Woo", (+1)) <*> (" Hoo!",0)
("Woo Hoo!",1)

The first elements of the pair were combined without explicitly supplying a function. What has happened, here?

No explanation for you. What about this?

·∾ import Data.Monoid

·∾ (Sum 2, (+1)) <*> (Sum 0, 0)
(Sum {getSum = 2},1)

·∾ (Sum 2,(+1)) <*> (Sum 0,0)
(Sum {getSum = 2},1)

·∾ (Product 3,(+9)) <*> (Product 2,8)
(Product {getProduct = 6},17)

·∾ (All True,(+1)) <*> (All False,0)
(All {getAll = False},1)

17.4.2 Tuple Monoid and Applicative side by side

Squint if you can’t see it:

instance (Monoid a, Monoid b) => Monoid (a,b) where
  mempty                   =          (mempty, mempty)
  (a,b) `mappend` (a',b')  =  (a `mappend` a', b `mappend` b')

instance Monoid a => Applicative ((,) a) where
  pure x                   =          (mempty, x)
  (u,f) <*> (v,x)          =   (u `mappend` v, f x)

17.4.3 Maybe Monoid and Applicative

While applicatives are monoidal functors, be careful about making assumptions based on this.

For one thing, Monoid and Applicative instances aren’t required or guaranteed to have the same monoid of structure, and the functorial part may change the way it behaves.

17.5 Applicative in use

This section provides usage examples of several instances of Applicative.

17.5.1 List Applicative

Let’s start by specializing the types:

-- 17.5.1 List Applicative
-- page 690


-- f ≡ []
(<*>) ::   f   (a -> b)   ->  f  a  ->  f  b
(<*>) ::  [ ]  (a -> b)   -> [ ] a  -> [ ] b

-- more syntactically typical
(<*>) ::  [(a -> b)] -> [a] -> [b]

pure :: a ->  f  a
pure :: a -> [ ] a


-- https://gitlab.haskell.org/ghc/ghc/-/wikis/type-application

-- Or, using GHCi, you can do this:

·∾ :set -XTypeApplications

·∾ :type (<*>) @[]
(<*>) @[] :: [a -> b] -> [a] -> [b]

·∾ :type pure @[]
pure @[] :: a -> [a]

17.5.2 What’s the List applicative do?

Previously with list Functor, we were mapping a single function over a plurality of values:

·∾ fmap (2^) [1,2,3]
[2,4,8]

With the list applicative, we are mapping a plurality of functions over a plurality of values.

·∾ [(+1),(*2)] <*> [2,4]
[3,5,4,8]

Now what happened with that expression we tested? Something like this:

[(+1),(*2)] <*> [2,4][(+1)2,(+1)4,(*2)2,(*2)4][3,5,4,8]

Apply maps each function value from the first list over the second list, applies the operation, and returns one list.

The liftA2 function gives us another way to write this, too:

·∾ import Control.Applicative

·∾ liftA2 (+) [1,2] [3,5]
[4,6,5,7]

If you’re familiar with Cartesian products, this probably looks a lot like one, but with functions.

There are a ton of examples in this section that aren’t included inline in my notes here. So, here is a terminal recording of me typing all of them out, to serve as proof (to myself) that I actually did try them. It’s long and messy, so I don’t recommend you watch it.

Instead, you’ll find a more nicely formatted version of those ghci examples in the figures directory.

17.5.3 Exercises: Lookups

In the following exercises you will need to use these terms to make the expressions typecheck.

  1. pure  :: Applicative f =>            a -> f a

  2. (<$>) :: Functor f     =>     (a -> b) -> f a -> f b

  3. (<*>) :: Applicative f =>   f (a -> b) -> f a -> f b

Make the following expressions typecheck:

  1. added :: Maybe Integer
    added = (+3) (lookup 3 $ zip [1,2,3] [4,5,6])
    

    Alright, here is the first one:

    
    added :: Maybe Integer
    added = pure (+3) <*> (lookup 3 $ zip [1,2,3] [4,5,6])
    
    
    
  2. y :: Maybe Integer
    y =  lookup 3 $ zip [1,2,3] [4,5,6]
    
    z :: Maybe Integer
    z = lookup 2 $ zip [1,2,3] [4,5,6]
    
    tupled :: Maybe (Integer, Integer)
    tupled = (,) y z
    

    Now it typechecks!

    
    y :: Maybe Integer
    y =  lookup 3 $ zip [1,2,3] [4,5,6]
    
    z :: Maybe Integer
    z = lookup 2 $ zip [1,2,3] [4,5,6]
    
    tupled :: Maybe (Integer, Integer)
    tupled = pure (,) <*> y <*> z
    
    
    
  3. import Data.List (elemIndex)
    
    x :: Maybe Int
    x = elemIndex 3 [1,2,3,4,5]
    
    y :: Maybe Int
    y = elemIndex 4 [1,2,3,4,5]
    
    max' :: Int -> Int -> Int
    max' = max
    
    maxed :: Maybe Int
    maxed = max' x y
    

    This should work:

    
    x' :: Maybe Int
    x' = elemIndex 3 [1,2,3,4,5]
    
    y' :: Maybe Int
    y' = elemIndex 4 [1,2,3,4,5]
    
    max' :: Int -> Int -> Int
    max' = max
    
    maxed :: Maybe Int
    maxed = pure max' <*> x' <*> y'
    
    
    
  4. as = [1,2,3]
    bs = [4,5,6]
    
    a :: Maybe Integer
    a = lookup 3 (zip as bs)
    
    b :: Maybe Integer
    b = lookup 2 (zip as bs)
    
    summed :: Maybe Integer
    summed = sum $ (,) a b
    

    This should work:

    
    as = [1,2,3]
    bs = [4,5,6]
    
    a :: Maybe Integer
    a = lookup 3 (zip as bs)
    
    b :: Maybe Integer
    b = lookup 2 (zip as bs)
    
    summed :: Maybe Integer
    summed =
      sum <$> (pure (,) <*> a <*> b)
    

17.5.4 Identity

The Identity type here is a way to introduce structure without changing the semantics of what we’re doing.

Specializing the types

Here is what the type will look like when our structure is Identity

-- 17.5.4 Identity
-- Specializing the types
-- page 704

-- This type comes from the last chapter on Foldable
data Identity a = Identity a

type Id = Identity

-- f ~ Identity
-- Applicative f =>

(<*>) ::  f (a -> b)  ->   f a  ->   f b
(<*>) :: Id (a -> b)  ->  Id a  ->  Id b

pure :: a ->  f a
pure :: a -> Id a

17.6 Applicative laws

After examining the law, try each expression in the REPL.

Identity

id embedded in an applicative applied to a value results in that value:

pure id <*> v   =   v

Composition

The result of composing our functions first and then applying them should be the same as the result of applying our functions first and then composing them.

pure (.) <*> u <*> v <*> w   =   u <*> (v <*> w)

This law is meant to ensure that there are no surprises resulting from composing our function applications.

Homomorphism

A homomorphism is a structure-preserving map between two structures.

pure f <*> pure x   =   pure (f x)

The general idea is that applying the function doesn’t change the structure around the value.

Interchange

Here is a rephrasing of the interchange law without ($) that I find easier to read:

a <*> pure v  =  pure (\f -> f v) <*> a

In this phrasing,

  • a is an applicative with a function in it, and

  • v is an applicative with a value in it.

An applicative containing functions applied to an applicative of values in the same context is equivalent to applying that function to every member element of the applicative containing values.

17.7 You knew this was coming

This section has you set up a project to property test the Applicative type class laws using a hackage package known as checkers.

Check out exercises/17.7.1_-_bad_monoid.rst.d/bad-monoid/ for more.

17.9 Chapter Exercises

17.9.1 Specialize the types

Given a type that has an instance of Applicative, specialize the types of the methods. Test your specialization in the REPL.

One way to do this is to bind aliases of the typeclass methods to more concrete types that have the type we told you to fill in.

  1. -- Type
    []
    
    -- Methods
    pure :: a -> ? a
    (<*>) :: ? (a -> b) -> ? a -> ? b
    

    This one can be solved with ghci:

    ·∾ :set -XTypeApplications
    
    ·∾ :type pure @[]
    pure @[] :: a -> [a]
    
    ·∾ :type (<*>) @[]
    (<*>) @[] :: [a -> b] -> [a] -> [b]
    
  2. -- Type
    IO
    
    -- Methods
    pure :: a -> ? a
    (<*>) :: ? (a -> b) -> ? a -> ? b
    

    This one can also be solved directly with ghci:

    ·∾ :type pure @IO
    pure @IO :: a -> IO a
    
    ·∾ :type (<*>) @IO
    (<*>) @IO :: IO (a -> b) -> IO a -> IO b
    
  3. -- Type
    (,) a
    
    -- Methods
    pure :: a -> ? a
    (<*>) :: ? (a -> b) -> ? a -> ? b
    
  4. -- Type
    (->) e
    
    -- Methods
    pure :: a -> ? a
    (<*>) :: ? (a -> b) -> ? a -> ? b
    

17.9.2 Write Applicative instances

Write instances for the following datatypes. Confused? Write out what the type should be. Use the checkers library to validate the instances.

  1. data Pair a = Pair a a deriving Show
    
  2. This should look familiar.

    data Two a b = Two a b
    
  3. data Three a b c = Three a b c
    
  4. data Three' a b = Three' a b b
    
  5. data Four a b c d = Four a b c d
    
  6. data Four' a b = Four' a a a b
    

17.9.3 Combinations

Remember the vowels and stops exercise in the folds chapter? Write the function to generate the possible combinations of three input lists using liftA3 from Control.Applicative.

import Control.Applicative (liftA3)
stops :: String
stops = "pbtdkg"

vowels :: String
vowels = "aeiou"

combos :: [a] -> [b] -> [c] -> [(a, b, c)]
combos = undefined