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.