/ Haskell Series

Applicatives by example

Running :i Applicative in ghci yields the following:

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

It also lists available instances of the Applicative type class:

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

Concrete type signatures

To understand similarities and differences between various instances of the Applicative type class we will take a look at the type signatures of the pure, <*> and liftA2 functions for List, Maybe and Either types:

-- Generic
pure :: a -> f a

-- List
pure :: a -> [a]

-- Maybe
pure :: a -> Maybe a

-- Either
pure :: a -> Either l a
-- Generic
(<*>) :: f (a -> b) -> f a -> f b

-- List
(<*>) :: [(a -> b)] -> [a] -> [b]

-- Maybe
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b

-- Either
(<*>) :: Either l (a -> b) -> Either l a -> Either l b
-- Generic
liftA2 :: (a -> b -> c) -> f a -> f b -> f c

-- List
liftA2 :: (a -> b -> c) -> [a] -> [b] -> [c]

-- Maybe
liftA2 :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c

-- Either
liftA2 :: (a -> b -> c) -> Either l a -> Either l b -> Either l c

Using pure with different types

pure lifts a value to a given type. The type can be inferred or specified with a type annotation like in the examples below:

-- List
(pure 2 :: [Integer]) == [2]
-- Maybe
(pure 2 :: Maybe Integer) == Just 2
-- Either
(pure 2 :: Either a Integer) == Right 2

Interactive Examples:


Using <*> with different types

In order to use <*> we need to define some functions we want to work with. After they are defined we will apply them to some instances of the Applicative type class:

addOne :: Num a => a -> a
addOne a = a + 1

addTwo :: Num a => a -> a
addTwo a = a + 2
-- List
[      ]         <*> [ ]    == [ ]  
[      ]         <*> [2]    == [ ]
[addOne]         <*> [ ]    == [ ]
[addOne]         <*> [2]    == [3]
[addOne, addTwo] <*> [ ]    == [ ]
[addOne, addTwo] <*> [2]    == [3, 4]
[addOne, addTwo] <*> [2, 3] == [3, 4, 4, 5]
-- Maybe
Nothing     <*> Nothing == Nothing
Nothing     <*> Just 2  == Nothing
Just addOne <*> Nothing == Nothing
Just addOne <*> Just 2  == Just 3
-- Either
Left  addOne <*> Left  2 == Left  addOne
Left  addOne <*> Right 2 == Left  addOne
Right addOne <*> Left  2 == Left  2
Right addOne <*> Right 2 == Right 3

Interactive Examples:


Using liftA2 with different types

To use liftA2 we also need to define a function. The function will accept two parameters and return a single value. After the function is defined we will apply it to some instances of the Applicative type class:

add :: Num a => a -> a -> a
add a b = a + b
-- List
import Control.Applicative (liftA2)

liftA2 add [ ]   [ ]   == []
liftA2 add [1]   [ ]   == []
liftA2 add [1]   [2]   == [3]
liftA2 add [1,2] [2]   == [3,4]
liftA2 add [1,2] [2,3] == [3,4,4,5]
-- Maybe
import Control.Applicative (liftA2)

liftA2 add (Nothing) (Nothing) == Nothing
liftA2 add (Nothing) (Just 2)  == Nothing
liftA2 add (Just 1)  (Nothing) == Nothing
liftA2 add (Just 1)  (Just 2)  == Just 3
-- Either
import Control.Applicative (liftA2)

liftA2 add (Left  1) (Left  2) == Left  1
liftA2 add (Left  1) (Right 2) == Left  1
liftA2 add (Right 1) (Left  2) == Left  2
liftA2 add (Right 1) (Right 2) == Right 3

Interactive Examples:


Applicatives are not scary either. They are pretty useful as you can see above. Let me know on twitter if you have any comments - @maciejsmolinski.

In the next post you will learn about Monads.