Functor Factory
A type is a functor if it can represent a “context” for values of some arbitrary type that we can apply functions to.
class Functor f where
fmap :: (a -> b) -> f a -> f b
data Maybe a = Nothing | Just a
instance Functor Maybe where
Pro Tip. When writing down the instance of a type class like functor
here, always write down the specialised type signatures of the function we’re defining first
-- fmap :: (a -> b) -> Maybe a -> Maybe b
fmap f Nothing = Nothing
fmap f (Just x) = Just $ f x
data Tree a = Leaf a | Node (Tree a) (Tree a)
instance Functor Tree where
-- fmap :: (a -> b) -> Tree a -> Tree b
fmap f (Leaf x) = Leaf (f x)
fmap f (Node l r) = Node (fmap f l) (fmap f r)
Functor Laws
A type f
is a functor if there is a function
fmap :: (a -> b) -> f a -> f b
and the following laws apply for it:
fmap id = id
fmap (f . g) = fmap f. fmap g
These laws imply that a data structure’s “shape” does not change when we use
fmap
- we are just operating/changing the elements inside the data structure.
Applicative Academy
A type is an applicative functor if its values represent some form of context that we can lift function application into.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
data Maybe a = Nothing | Just a
instance Applicative Maybe where
-- pure :: a -> Maybe a
pure x = Just x
-- (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
(Just f) <*> (Just x) = Just (f x)
_ <*> _ = Nothing
Suppose that f is a function with two parameters e.g.
f :: a -> b -> c
f <$> x <*> y
= fmap f x <*> y
= pure f <*> x <*> y
Applicative Laws
In Haskell, a type f
is an applicative functor if there are functions pure
and <*>
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
and the following laws apply for them:
pure id <*> x = x
pure f <*> pure x = pure (f x)
m <*> pure y = pure ($ y) <*> m
pure (.) <*> x <*> y <*> z = x <*> (y <*> z)
Left and Right Apply
(<*) :: Applicative f => f a -> f b -> f a
x <* y = const <$> x <*> y
(*>) :: Applicative f => f a -> f b -> f a
x *> y = flip const <$> x <*> y
Limitations of Applicatives
Applicative effects cannot depend on the result of the previous computation, which is why we have monads.
Consider the example of looking up the grandmother of a person from a dictionary which maps people to their mothers. This dictionary can be represented as a value of type [(String, String)]
and we can then use the lookup :: Eq a => a -> [(a,b)] -> Maybe b
function from the standard library to retrieve the name of the mother of a given person:
grandmother :: String -> [(String, String)] -> Maybe String
grandmother x dict = do
mother <- lookup x dict
lookup mother dict
If there is no mapping for a person’s name to their mother, then Nothing
is returned. Therefore, to look up a person’s grandmother, we first need to look up their mother’s name and then the name of their mother’s mother.
Writer Type
data Writer w a = MkWriter (a,w)
The writer type is a good example of a functor that is not an applicative functor.
instance Functor (Writer w) where
fmap f (MkWriter (x,o)) = MkWriter (f x, o)
It cannot be an applicative functor because we need to be able to write an instance for pure
. Looking at the typing for pure
, we can see that we need to use the MkWriter
constructor, which expects a pair of type (a,w)
as argument. While we have a value of type a
that can be given to pure
as argument, we do not have a value for w
.
pure :: a -> Writer w a
Hence, since it is not possible to write a suitable definition, the Writer
type is not an applicative functor. Even if we find some way to write a definition, it will not obey the relevant applicative laws.
State Type
State
has two parameters and therefore is a of kind * -> * -> *
. From type signature of St
, we can see that we are storing functions that produces pairs.
data State s a = St (s -> (a,s))
St :: (s -> (a,s)) -> State s a
runState :: State s a -> s -> (a,s)
runState (St m) = m
A value of type State s a
represents a computation that accepts and initial state of type s
and uses it to produce a result of type a
and a resulting state of type s
.
The main idea behind this is that by combining values of type
State
in some way, we could automatically propagate state throughout a program in a way that appears as if the state was mutable.
Monad Merry-Go-Round
A type is a monad if its value represent some form of context that permits us to apply a function to all the elements in one context, producing one new context each which can then all be joined together into one final context.
class Applicative m => Monad m where
(>>=) :: m a -> (a -> m b) -> m b
data Maybe a = Nothing | Just a
instance Monad Maybe where
-- (>>=) :: Maybe a -> (a -> m b) -> m b
(Just x) >>= f = f x
Nothing >>= f = Nothing
You can also characterise a monad with the join
function.
join :: Monad m => m (m a) -> m a
join mm = mm >>= id
(>>=) :: Monad m => m a -> (a -> m b) -> m b
m >>= f = join (fmap f m)
In theory, either function characterises a monad and can be used to define the other function. Haskell’s Monad
type class requires us to define >>=
, while join
is defined in terms of >>=
Monad Laws
In Haskell, a type m
is a monad if there are functions return
and >>=
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
and the following laws apply for them:
-- Left Identity
return x >>= f = f x
-- Right Identity
m >>= return = m
-- Associativity
(m >>= f) >>= g = m >>= (\x -> f x >>= g)
Additional resources
The following chapters in Learn You a Haskell for Great Good! A Beginner’s Guide, Lipovaca, Miran cover functors, applicative functors and monads in gratuitous detail:
- The functor typeclass
- Functors, applicative functors, and monoids
- A fistful of monads
- For a few monads more
However, a simpler more intuitive description of them (which does skim over some points), is available here: