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 value with the result of the recursive call.

Tail recursion performs its work by combining a value 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 regular recursion:

foo f v []     = v
foo f v (x:xs) = x `f` foo f v xs

Tail Recursive Pattern

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

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

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

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.