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 element with the result of the recursive call.
Tail recursion performs its work by combining an element 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 recursion:
foo f v [] = v
foo f v (x:xs) = x `f` foo f v xs
Now we can identify a pattern for tail recursion:
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
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)]
Define the zip’ function using foldl, without using zip or list comprehensions.
zip' :: [a] -> [b] -> [(a, b)]
zip' xs ys = foldl go [] [0..(minLen - 1)]
where
minLen = min (length xs) (length ys)
go acc n = acc ++ [(xs !! n, ys !! n)]
Does this fully satisfy the requirements?
> zip [0..] [1,2,3]
[(0,1),(1,2),(2,3)]
> zip' [0..] [1,2,3]
...
It loops forever because it tries to find the length of an infinite list!
Can we fix it?
Kinda…
zip' :: [a] -> [b] -> [(a, b)]
zip' xs ys = foldl go [] [0..(lazyMinLen xs ys)]
where
lazyMinLen [] bs = length xs - 1
lazyMinLen as [] = length ys - 1
lazyMinLen (a:as) (b:bs) = lazyMinLen as bs
go acc n = acc ++ [(xs !! n, ys !! n)]
Meh, I don’t like this solution since lazyMinLen is basically reimplementing zip.
Can you think of something more elegant?
How about this:
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)
Still fails for infinite lists in the second position but not the first why?
foldl must reach the end of the list in order to terminate.
We will revisit this problem later on.