Introducing Monads
The Functor, Applicative, and Monad typeclasses are pervasive across Haskell programs. These type classes help you address a wide variety of extremely common problems that arise when writing any number of real world programming problems.
You’ll learn about monads, applicatives, and functors and what laws govern them. You’ll also learn more about how to work with IO actions, and how to make use of other common data types, like the optional Maybe type.
You’ll be able to work effectively with any datatype that is a functor, applicative, or monad, and define your own instances for these type classes.
Throughout the remainder of this book we’ll make use of these typeclasses regularly.
Flipping the Script
Try to write instances of Functor
, Applicative
, and Monad
for List
where
Functor
is defined in terms of Applicative
, and Applicative
is defined in
terms of Monad
. Is this possible? Why or why not?
Hint 1
Some high level hint text
Hint 2
Some more detailed hint text
Hint 3
Even more detailed hint text
Solution
A complete solution for the exercise
Not a Functor
Imagine that we’ve created a new type to represent a sorted list of values:
{-# LANGUAGE DerivingStrategies #-}
module SortedListFunctor (SortedList, insertSorted) where
data SortedList a = Empty | Cons a (SortedList a)
deriving stock (Eq, Show)
insertSorted :: Ord a => a -> SortedList a -> SortedList a
Empty = Cons a Empty
insertSorted a Cons b bs)
insertSorted a (| a >= b = Cons b (insertSorted a bs)
| otherwise = Cons a (Cons b bs)
Although SortedList
might be useful, it turns out that you can’t write a
correct instance of Functor
for a SortedList
. Try to define Functor
yourself and experiment with it’s behavior. See if you can figure out why you
can’t write a correct instance.
Hint 1
Some high level hint text
Hint 2
Some more detailed hint text
Hint 3
Even more detailed hint text
Solution
A complete solution for the exercise
The Extended Functor Family
In addition to the standard Functor
class that you’ve used in this chapter,
there are other type classes that are related to Functor
but with somewhat
different behaviors.
Bifunctors
A Bifunctor
is like a Functor
but even moreso, because a Bifunctor
let’s
you map two different fields. The Bifunctor
class is defined in
Data.Bifunctor
. Let’s take a look at a definition for it:
class Bifunctor f where
bimap :: (a -> c) -> (b -> d) -> f a b -> f c d
first :: (a -> c) -> f a b -> f c b
= bimap f id
first f
second :: (b -> d) -> f a b -> f a d
= bimap id f second f
Try to write an instance of Bifunctor
for Either
.
Contravariant Functors
The Contravariant
class from Data.Functor.Contravariant
in base defines a
_contravariant_
functor. Although we don’t normally refer to them this way,
the Functor
class that you’ve been working with so far is a
class Contravariant f where
contramap :: (b -> a) -> f a -> f b
Try to create a new version of the Function
type that you defined earlier, and
then write an instance of Contravariant
for it. Can you also create an
instance of Contravariant
for your original definition of Function
? Why or
why not?
Profunctors
A Profunctor
is a combination of a Bifunctor
and a Contravariant
functor. Profunctor
isn’t defined in base
, but you’ll see it defined by some
other popular libraries. Like a Bifunctor
it works on types with two
arguments. Like Contravariant
functors, the first argument to a Profunctor
works “backwards”. Let’s take a look at a definition for Profunctor
:
class Profunctor f where
dimap :: (c -> a) -> (b -> d) -> f a b -> f c d
lmap :: (c -> a) -> f a b -> f c b
= dimap f id
lmap f
rmap :: (b -> d) -> f a b -> f a d
= dimap id f rmap f
Try to create an instance of Profunctor
for your original Function
type. Can
you write a valid instance? Why or why not? How does this differ from trying to
create a instance of Contravariant
for Function
?
Hint 1
Some high level hint text
Hint 2
Some more detailed hint text
Hint 3
Even more detailed hint text
Solution
A complete solution for the exercise