Applicative is a monoidal functor. Monoid mashes two values of the same type together.
Functor, on the other hand, is for function application over some structure while
keeping the structure untouched. Monoid’s core operation, mappend
smashes the structures
together, so the structures themselves have been joined. However, the core operation of
Functor, fmap
applies a function to a value that is within some structure while leaving
that structure unaltered.
Applicative
The Applicative typeclass allows for function application lifted over structure (like Functor). But with Applicative the function we’re applying is also embedded in some structure. Because the function and the value it’s being applied to both have structure, we have to smash those structures together. So, Applicative involves monoids and functors.
1
2
3
4
5
class Functor => Applicative (f :: * -> *) where
pure :: a -> fa
(<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
The pure
function does a simple and very boring thing: it embeds something into functorial
(applicative) structure which can be thought as being a bare minimum bit of structure or
structural identity. The more core operation of this typeclass is <*>
.
Note the similarity between ($)
, (<$>)
and (<*>)
:
1
2
3
4
5
($) :: (a -> b) -> a -> b
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Lift
Along with these core functions, the Control.Applicative
library provides some other convenient
functions: liftA
, liftA2
, and liftA3
:
1
2
3
4
5
liftA :: Applicative f => (a -> b) -> f a -> f b
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d
The type signature of liftA
is almost identical to fmap
, and it is functionally as well.
1
2
3
4
5
fmap (+1) [1,2,3]
-- [2,3,4]
liftA (+1) [1,2,3]
-- [2,3,4]
The relationship between liftA
and fmap
is like:
1
2
3
4
5
fmap :: Functor f => (a -> b) -> f a -> f b
-- fmap func (f a) = f (func a)
liftA :: Applicative f => (a -> b) -> f a -> f b
-- liftA func (f a) = pure func <*> (f a) = f (func a)
With liftA2
we can write something as follows:
1
2
3
4
5
6
7
-- write
(,) <$> [1,2] <*> [3,4]
-- [(1,3), (1,4), (2,3), (2,4)]
-- as
liftA2 (,) [1,2] [3,4]
-- [(1,3), (1,4), (2,3), (2,4)]
, similar for liftA3
.
Law
Applicative instances must abide by the following laws:
1
2
3
4
5
6
7
8
9
10
11
-- identity
pure id <*> v = v
-- composition
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
-- homomorphism
pure f <*> pure x = pure (f x)
-- interchange
u <*> pure y = pure ($ y) <*> u
Note that, a homomorphism is a structure-preserving map between two categories. The effect of applying a function that is embedded in some structure to a value that is embedded in some structure should be the same as applying a function to a value without affecting any outside structure.