This week we studied more Haskell topics:
type synonyms
folds
datatypes
derived instances
recursive data structures
You can review these topics in chapters 6 and 8 of the Learn You a Haskell book.
foldr :: (b → a → a) → a → [b] → a
The function passed to foldr takes two arguments: a list element and an accumulator. The accumulator is the second argument: it comes from the right, as you can see in the examples below. The accumulator is combined with the last list element.
foldr f a [x1, x2, x3, x4, …, xn] = f x1 (f x2 (f x3 (f x4 (… (f xn a) …)))
foldr (+) 0 [2, 4, 6, 8, …, 20] = 2 + (4 + (6 + (8 + … (20 + 0) … )))
foldr (:) [] [2, 4, 6, 8, …, 20] = 2 : 4 : 6 : 8 : … : 20 : []
foldr captures the pattern of recursion that occurs when we write functions directly recursively. For example, instead of
sum :: Num a => [a] -> a sum [] = 0 sum (x : xs) = x + sum xs
we may write
sum :: Num a => [a] -> a sum = foldr (+) 0
foldl :: (a → b → a) → a → [b] → a
The function passed to foldl takes two arguments: an accumulator and a list element. The accumulator is the first argument: it comes from the left, as you can see in the examples below. The accumulator is combined with the first list element.
foldl f a [x1, x2, x3, x4, …, xn] = f (… (f (f (f a x1) x2) x3) … xn)
foldl (+) 0 [2, 4, 6, 8, …, 20] = (… ((((0 + 2) + 4) + 6) + 8) … + 20)
foldl captures the pattern of recursion that occurs when we write functions using a helper function with an extra accumulator argument. For example, instead of
rev :: [a] -> [a] rev list = let f [] a = a f (x : xs) a = f xs (x : a) in f list []
we may write
rev :: [a] -> [a] rev = foldl (\a v -> v : a) []
Here are some exercises we solved in the tutorial.
Write map
and filter
using foldl
and/or foldr
.
map :: (a -> b) -> [a] -> [b] map f = foldr (\x a -> f x : a) [] filter :: (a -> Bool) -> [a] -> [a] filter f = foldr (\x a -> if f x then x : a else a) []
Write a
function treeVals
that takes a binary search tree and returns a list of all its values
in order.
This direct implementation runs in O(N^2) in the worst case:
treeVals :: Ord a => Tree a -> [a] treeVals Nil = [] treeVals (Node left x right) = treeVals left ++ [x] ++ treeVals right
This version uses a helper function with an extra accumulator argument, and runs in O(N):
treeVals :: Ord a => Tree a -> [a] treeVals tree = -- (f tree acc) will find all values in the given tree and _prepend_ them -- to the given accumulator let f :: Tree a -> [a] -> [a] f Nil acc = acc f (Node left x right) acc = let rlist = f right acc -- values in the right subtree, prepended to acc in f left (x : rlist) in f tree []
Design a data type Exp that can represent any arithmetic expression involving integer constants, named variables, and the +, - and * operators.
data Op = Plus | Minus | Times data Expr = Const Int | Var String | OpExpr Expr Op Expr
Write a function that evaluates an expression given an environment, which is an association list that holds the variables of the variables in the expression.
opfun :: Op -> (Int -> Int -> Int) opfun Plus = (+) opfun Minus = (-) opfun Times = (*) eval :: Expr -> [(String, Int)] -> Int eval expr env = case expr of Const n -> n Var v -> fromJust (lookup v env) OpExpr left op right -> let l = eval left env r = eval right env in opfun op l r
Pretend that Haskell does not have a built-in
list type. Implement lists using an abstract data type DList
a
.
data DList a = Nil | Cons a (DList a) deriving (Eq, Ord)
Write a function that computes the sum of all values in a DList
Int
.
dsum :: DList Int -> Int dsum Nil = 0 dsum (Cons x rest) = x + dsum rest
Write a function that performs a right fold over a DList
a
.
dfoldr :: (b -> a -> a) -> a -> DList b -> a dfoldr _ a Nil = a dfoldr f a (Cons x rest) = f x (dfoldr f a rest)