Joseph Haugh
University of New Mexico
How can you express this list comprehension
using map and filter?
[ f x | x <- xs, p x ]map f (filter p xs)
Define the all function which takes a predicate, a -> Bool, and returns True if the predicate is True for all elements
all :: (a -> Bool) -> [a] -> Bool
Define the all’ function which takes a predicate, a -> Bool, and returns True if the predicate is True for all elements
all' :: (a -> Bool) -> [a] -> Bool
all' p [] = True
all' p (x:xs) = p x && all' p xs
Define all’ using foldr
all' :: (a -> Bool) -> [a] -> Bool
Define all’ using foldr
all' :: (a -> Bool) -> [a] -> Bool
all' p xs = foldr (\x rest -> p x && rest) True xs
Define all’ using tail recursion
all' :: (a -> Bool) -> [a] -> Bool
Define all’ using tail recursion
all' :: (a -> Bool) -> [a] -> Bool
all' p xs = go True xs
where
go acc [] = acc
go acc (x:xs) = go (acc && p x) xs
Recursion normally performs its work by combining an value with the result of the recursive call.
Tail recursion performs its work by combining a value with an accumulator.
-- recursive
sum :: Num a => [a] -> a
sum [] = 0
sum (x:xs) = x + sum xs
-- tail recursive
sum :: Num a => [a] -> a
sum xs = go 0 xs
where
go acc [] = acc
go acc (x:xs) = go (acc + x) xs
Recursive:
sum [1,2,3]
| { applying sum }
1 + (sum [2,3])
| { applying sum }
1 + (2 + sum [3])
| { applying sum }
1 + (2 + (3 + sum []))
| { applying sum }
1 + (2 + (3 + 0))
| { applying + }
6
Tail Recursive:
sum [1,2,3]
| { applying sum }
go 0 [1,2,3]
| { applying go }
go (0 + 1) [2,3]
| { applying go }
go ((0 + 1) + 2) [3]
| { applying go }
go (((0 + 1) + 2) + 3) []
| { applying go }
(((0 + 1) + 2) + 3)
| { applying + }
6
Recall the pattern we identified for regular recursion:
foo f v [] = v
foo f v (x:xs) = x `f` foo f v xs
Can you spot the pattern between these two functions?
all :: (a -> Bool) -> [a] -> Bool
all p xs = go True xs
where
go acc [] = acc
go acc (x:xs) = go (acc && p x) xs
sum :: Num a => [a] -> a
sum xs = go 0 xs
where
go acc [] = acc
go acc (x:xs) = go (acc + x) xs
Perhaps something likes this:
foo f v [] = v
foo f v (x:xs) = foo f (v `f` x) xs
foldl abstracts this pattern:
sum :: Num a => [a] -> a
sum xs = foldl (+) 0 xs
product :: Num a => [a] -> a
product xs = foldl (*) 1 xs
and :: [Bool] -> Bool
and xs = foldl (&&) True xs
all :: (a -> Bool) -> [a] -> Bool
all p xs = foldl (\acc x -> acc && p x) True xs
How does foldl differ from foldr?
To start let’s look at their types:
foldr :: (a -> b -> b) -> b -> [a] -> b
foldl :: (b -> a -> b) -> b -> [a] -> b
Recall our general pattern:
foo f v [] = v
foo f v (x:xs) = foo f (v `f` x) xs
Notice that our f function is applied to the v, type b, first and then the x, type a.
That is why the type of foldl has the b first
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f v [] = v
foldl f v (x:xs) = foldl f (v `f` x) xs
Define the length function using foldl
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f v [] = v
foldl f v (x:xs) = foldl f (v `f` x) xs
length :: [a] -> Int
Define the length function using foldl
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f v [] = v
foldl f v (x:xs) = foldl f (v `f` x) xs
length :: [a] -> Int
length xs = foldl (\acc _ -> acc + 1) 0 xs
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f v [] = v
foldl f v (x:xs) = foldl f (v `f` x) xs
length [1,2,3]
| { applying length }
foldl (\acc _ -> acc + 1) 0 [1,2,3]
| { applying foldl }
foldl (\acc _ -> acc + 1) (0 + 1) [2,3]
| { applying foldl }
foldl (\acc _ -> acc + 1) ((0 + 1) + 1) [3]
| { applying foldl }
foldl (\acc _ -> acc + 1) (((0 + 1) + 1) + 1) []
| { applying foldl }
(((0 + 1) + 1) + 1)
| { applying + }
3
Define the reverse function using foldl
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f v [] = v
foldl f v (x:xs) = foldl f (v `f` x) xs
reverse :: [a] -> [a]
Define the reverse function using foldl
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f v [] = v
foldl f v (x:xs) = foldl f (v `f` x) xs
reverse :: [a] -> [a]
reverse xs = foldl (\acc x -> x : acc) [] xs
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f v [] = v
foldl f v (x:xs) = foldl f (v `f` x) xs
reverse [1,2,3]
| { applying reverse }
foldl (\acc x -> x : acc) [] [1,2,3]
| { applying foldl }
foldl (\acc x -> x : acc) (1 : []) [2,3]
| { applying foldl }
foldl (\acc x -> x : acc) (2 : (1 : [])) [3]
| { applying foldl }
foldl (\acc x -> x : acc) (3 : (2 : (1 : []))) []
| { applying foldl }
3 : (2 : (1 : []))
| { applying : }
[3,2,1]
Recall how foldr can be thought of as replacing every (:) with f and [] with v.
foldr f v [1,2,3] = 1 `f` (2 `f` (3 `f` v))
A similar intuition applies to foldl but the evaluation goes from left to right and the starting value is on the left.
foldl f v [1,2,3] = (((v `f` 1) `f` 2) `f` 3)
Define the map’ function using foldl
map' :: (a -> b) -> [a] -> [b]
Define the map’ function using foldl
map' :: (a -> b) -> [a] -> [b]
map' f xs = foldl (\acc x -> acc ++ [f x]) [] xs
foldl f v [] = v
foldl f v (x:xs) = foldl f (v `f` x) xs
map' f xs = foldl (\acc x -> acc ++ [f x]) [] xs
map' (+1) [1,2,3]
| { applying map' }
foldl (\acc x -> acc ++ [f x]) [] [1,2,3]
| { applying foldl }
foldl (\acc x -> acc ++ [f x]) ([] ++ [(+1) 1]) [2,3]
| { applying foldl }
foldl (\acc x -> acc ++ [f x]) ([] ++ [(+1) 1] ++ [(+1) 2]) [3]
| { applying foldl }
foldl (\acc x -> acc ++ [f x]) ([] ++ [(+1) 1] ++ [(+1) 2] ++ [(+1) 3]) []
| { applying foldl }
[] ++ [(+1) 1] ++ [(+1) 2] ++ [(+1) 3]
| { applying ++ and (+1) }
[2,3,4]
Define the filter’ function using foldl
filter' :: (a -> Bool) -> [a] -> [a]
Define the filter’ function using foldl
filter' :: (a -> Bool) -> [a] -> [a]
filter' p xs =
foldl (\acc x -> if p x then acc ++ [x] else acc) [] xs
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f v [] = v
foldl f v (x:xs) = foldl f (v `f` x) xs
filter' even [1,2,3,4]
| { applying filter' }
foldl (\acc x -> if p x then acc ++ [x] else acc) [] [1,2,3,4]
| { applying foldl }
foldl (\acc x -> if p x then acc ++ [x] else acc) [] [2,3,4]
| { applying foldl }
foldl (\acc x -> if p x then acc ++ [x] else acc) ([] ++ [2]) [3,4]
| { applying foldl }
foldl (\acc x -> if p x then acc ++ [x] else acc) ([] ++ [2]) [4]
| { applying foldl }
foldl (\acc x -> if p x then acc ++ [x] else acc) (([] ++ [2]) ++ [4]) []
| { applying foldl }
([] ++ [2]) ++ [4]
| { applying ++ }
[2,4]
Define the dec2Int function using foldl
> dec2Int [2,3,4,5]
2345
dec2Int :: [Int] -> Int
Define the dec2Int function using foldl
dec2Int :: [Int] -> Int
dec2Int xs = foldl (\acc x -> acc * 10 + x) 0 xs
Recall the zip function which takes two lists and returns a list of pairs of elements from both lists:
> zip [1,2,3] [4,5,6]
[(1,4),(2,5),(3,6)]
> zip [1,2] [4,5,6]
[(1,4),(2,5)]
> zip [1,2,3] [4,5]
[(1,4),(2,5)]
Define the zip’ function using foldl
zip' :: [a] -> [b] -> [(a,b)]
zip' :: [a] -> [b] -> [(a, b)]
zip' xs ys = reverse (fst (foldl go ([], xs) ys))
where
go (acc, []) _ = (acc, [])
go (acc, x:xs) y = ((x, y) : acc, xs)
However, it loops forever for infinite lists in the second position but not the first why?
foldl must reach the end of the list given to it in order to terminate.
We will revisit this problem later on.