Lecture 14 Foldl

Joseph Haugh

University of New Mexico

Review

  • How can you express this list comprehension using map and filter?

    [ f x | x <- xs, p x ]
map f (filter p xs)

Review

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

Review

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

Review

Define all’ using foldr

all' :: (a -> Bool) -> [a] -> Bool

Review

Define all’ using foldr

all' :: (a -> Bool) -> [a] -> Bool
all' p xs = foldr (\x rest -> p x && rest) True xs

Review

Define all’ using tail recursion

all' :: (a -> Bool) -> [a] -> Bool

Review

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

Recall: Tail Recursion

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.

Example: sum

-- 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

Example: sum

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

Example: sum

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

Tail Recursive Pattern

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

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

Foldl

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

Foldl’s Type

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 Definition

foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f v []     = v
foldl f v (x:xs) = foldl f (v `f` x) xs

Exercise: length

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

Exercise: length

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

Exercise: length

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

Exercise: reverse

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]

Exercise: reverse

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

Exercise: reverse

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 foldr Intuition

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)

Exercise: map

Define the map’ function using foldl

map' :: (a -> b) -> [a] -> [b]

Exercise: map

Define the map’ function using foldl

map' :: (a -> b) -> [a] -> [b]
map' f xs = foldl (\acc x -> acc ++ [f x]) [] xs

Exercise: map

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]

Exercise: filter

Define the filter’ function using foldl

filter' :: (a -> Bool) -> [a] -> [a]

Exercise: filter

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

Exercise: filter

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]

Exercise: dec2Int

Define the dec2Int function using foldl

> dec2Int [2,3,4,5]
2345

dec2Int :: [Int] -> Int

Exercise: dec2Int

Define the dec2Int function using foldl

dec2Int :: [Int] -> Int
dec2Int xs = foldl (\acc x -> acc * 10 + x) 0 xs

Challenge Exercise: zip

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)]

Challenge Exercise: zip

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?

Challenge Exercise: zip

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?

Challenge Exercise: zip

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.