You can read about these subjects in Learn You a Haskell, chapters 8 and 9.
We've previously seen that the Functor
type class represents any type that we can map over using fmap
.
Similarly, the Foldable
type class
represents any type that we can fold over. In fact the
built-in functions foldl
and foldr
are members of this type class.
Certainly the built-in list type []
is an instance of Foldable
. In fact
Maybe
is also a Foldable
:
> foldl (+) 2 (Just 10) 12 > foldl (+) 2 Nothing 2
We can see that when folding, a Just
acts like a single-element list, and Nothing
acts like an empty list.
Many of the built-in functions that we've seen on
lists will actually work on any Foldable
.
These include all
, any
,
elem
, product
,
sum
, and others. So we can write, for
example:
> any (>3) (Just 5) True > any (>3) Nothing False > all (>3) (Just 5) True > all (>3) Nothing True > sum (Just 5) 5 > sum Nothing 0
We may declare our own types to be instance of Foldable
.
As one possible approach, we can provide an implementation of foldr
.
For example, let's define a binary tree type and make it foldable:
data Tree a = Nil | Node (Tree a) a (Tree a) deriving (Show, Read) instance Foldable Tree where foldr f a Nil = a foldr f a (Node left x right) = foldr f (f x (foldr f a right)) left
Because our type is Foldable
, many
useful built-in functions will now work on it automatically:
> my_tree = Node (Node (Node Nil 1 Nil) 2 (Node Nil 3 Nil)) 4 (Node Nil 5 Nil) > sum my_tree 15 > elem 3 my_tree True > all (>0) my_tree True
In fact Haskell will even implement foldl
automatically given our implementation of foldr
:
> foldl (+) 0 my_tree 15
There is another easier, approach that will work for algebraic data
types such as Tree
. We can simply
derive Foldable
, and then Haskell will
implement both foldr
and foldl
automatically:
data Tree a = Nil | Node (Tree a) a (Tree a) deriving (Show, Read, Foldable)
In Haskell, a do block is like a generalized list comprehension.
As a first example, we may use do
with ordinary lists. Consider this list comprehension:
> list = [(x, y) | x <- [1..2], y <- [1..3]] > list [(1,1),(1,2),(1,3),(2,1),(2,2),(2,3)]
We may alternatively write it using do
notation:
list = do x <- [1..2] y <- [1..3] return (x, y)
We see that in a do
block, we can use
the <- operator to retrieve a
value. At the end of the do
block, we can use the return
function to
produce a value to be
returned. When we use do
with ordinary
lists, we retrieve values from lists, and the return values are
joined together into a list.
We may also use do
with Maybe
. Here's a function that takes
three values of type (Maybe Int)
, and
returns their sum:
add :: Maybe Int -> Maybe Int -> Maybe Int -> Maybe Int add mx my mz = do x <- mx y <- my z <- mz return (x + y + z)
Let's try it:
> add (Just 3) (Just 4) (Just 5) Just 12 > add (Just 3) Nothing (Just 5) Nothing
Notice that if any of the input values is absent, the result is
Nothing
. To see why this is true, recall
that a Maybe
behaves like a list that
has 0 or 1 elements, and notice that the following do
block will produce an empty list:
list = do x <- [3] y <- [] z <- [5] return (x + y + z)
A do
block with Maybe
values can be quite useful for error handling. Suppose that we want
to call several functions, any of which might fail and return
Nothing
. If we place these calls in a do
block, then it will return Nothing
if
any of the function calls failed.
As we saw in the lecture (and you can also read
about in Learn You a Haskell), do
blocks
also work with I/O objects. In fact do
blocks will work with any instance of a type class called Monad
,
which plays a fundamental role in advanced Haskell programming. []
,
Maybe
and IO
are all instances of Monad
. In the next
lecture we'll learn more about the Monad
type class, and will learn how to make our own types that work with
do
.