Lecture 12 More Higher Order Functions

Joseph Haugh

University of New Mexico

Review

  • What does this expression evaluate to?

    map fst [(1,2), (3,4), (5,6)]
[1,3,5]

Review

  • What does this expression evaluate to?

    map (filter even) [[1,2,3], [4,5,6], [7,8,9]]
[[2], [4,6], [8]]

Review

  • What does this expression evaluate to?

    filter (any odd) [[1,2,3], [4,5,6], [7,8,9]]
[[1,2,3], [4,5,6], [7,8,9]]

Review: Map

map applies a function, f, to each element of a list, xs

map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs

Review: Filter

filter selects elements from a list, xs, that satisfy a predicate, pred

filter :: (a -> Bool) -> [a] -> [a]
filter pred [] = []
filter pred (x:xs)
    | pred x    = x : filter pred xs
    | otherwise = filter pred xs

Further Abstraction

Many list functions follow this pattern:

foo []     = v
foo (x:xs) = x # foo xs

Further Abstraction

foo []     = v
foo (x:xs) = x # foo xs

sum []     = 0
sum (x:xs) = x + sum xs

Further Abstraction

foo []     = v
foo (x:xs) = x # foo xs

sum []     = 0
sum (x:xs) = x + sum xs

product []     = 1
product (x:xs) = x * product xs

Further Abstraction

foo []     = v
foo (x:xs) = x # foo xs

sum []     = 0
sum (x:xs) = x + sum xs

product []     = 1
product (x:xs) = x * product xs

and []     = True
and (x:xs) = x && and xs

Further Abstraction

foo []     = v
foo (x:xs) = x # foo xs

sum []     = 0
sum (x:xs) = x + sum xs

product []     = 1
product (x:xs) = x * product xs

and []     = True
and (x:xs) = x && and xs

or []     = False
or (x:xs) = x || or xs

Further Abstraction

foo []     = v
foo (x:xs) = x # foo xs

sum []     = 0                  -- v
sum (x:xs) = x + sum xs         -- x # foo xs

product []     = 1              -- v
product (x:xs) = x * product xs -- x # foo xs

and []     = True               -- v
and (x:xs) = x && and xs        -- x # foo xs

or []     = False               -- v
or (x:xs) = x || or xs          -- x # foo xs

Higher Order Function: foldr

foldr abstracts this pattern:

sum xs = foldr (+) 0 xs

product xs = foldr (*) 1 xs

and xs = foldr (&&) True xs

or xs = foldr (||) False xs

Exercise: foldr’’

sum xs = foldr (+) 0 xs

product xs = foldr (*) 1 xs

and xs = foldr (&&) True xs

or xs = foldr (||) False xs

Try to write foldr’’

Exercise: foldr’’

Step 1: Define the type

What type does (+), (*), (&&), and (||) have?

(+) :: Num a => a -> a -> a
(*) :: Num a => a -> a -> a
(&&) :: Bool -> Bool -> Bool
(||) :: Bool -> Bool -> Bool

It appears we need a function with type a -> a -> a

Exercise: foldr’’

Step 1: Define the type

What type does the base case have?

It is the same type as the combining function (e.g. (+), (*), (&&), or (||)

Exercise: foldr’’

Step 1: Define the type

Lastly, what type are all these functions recursing over?

A list of the types the combining function takes

Exercise: foldr’’

Step 1: Define the type

Thus, our type is:

foldr'' :: (a -> a -> a) -> a -> [a] -> a

Exercise: foldr’’

Step 1: Define the type

Thus, our type is:

foldr'' :: (a -> a -> a) -> a -> [a] -> a
           (     #     )    v     xs

foo []     = v
foo (x:xs) = x # foo xs

Exercise: foldr’’

Step 2: Enumerate the cases

foldr'' f v []     = ...
foldr'' f v (x:xs) = ...

Exercise: foldr’’

Step 3: Define the easy cases

foldr'' f v []     = v
foldr'' f v (x:xs) = ...

Exercise: foldr’’

Step 4: Define the other cases

foldr'' f v []     = v
foldr'' f v (x:xs) = x `f` foldr'' f v xs

Exercise: foldr’’

Step 5: Generalize and simplify

Before we do this let’s see how foldr'' works step by step

Using foldr’’

foldr'' :: (a -> a -> a) -> a -> [a] -> a
foldr'' f v []     = v
foldr'' f v (x:xs) = x `f` foldr'' f v xs

foldr'' (+) 0 [1,2,3]
| { apply foldr'' }
1 + foldr (+) 0 [2,3]
| { apply foldr'' }
1 + (2 + foldr'' (+) 0 [3])
| { apply foldr'' }
1 + (2 + (3 + foldr'' (+) 0 []))
| { apply foldr'' }
1 + (2 + (3 + 0))
| { apply + }
6

Using foldr’’

foldr'' :: (a -> a -> a) -> a -> [a] -> a
foldr'' f v []     = v
foldr'' f v (x:xs) = x `f` foldr'' f v xs

foldr'' (*) 1 [1,2,3]
| { apply foldr'' }
1 * foldr'' (*) 1 [2,3]
| { apply foldr'' }
1 * (2 * foldr'' (*) 1 [3])
| { apply foldr'' }
1 * (2 * (3 * foldr'' (*) 1 []))
| { apply foldr'' }
1 * (2 * (3 * 1))
| { apply * }
6

Using foldr’’

foldr'' :: (a -> a -> a) -> a -> [a] -> a
foldr'' f v []     = v
foldr'' f v (x:xs) = x `f` foldr'' f v xs

foldr'' (&&) True [True, False, True]
| { apply foldr'' }
True && foldr'' (&&) True [False, True]
| { apply foldr'' }
True && (False && foldr'' (&&) True [True])
| { apply foldr'' }
True && (False && (True && foldr'' (&&) True []))
| { apply foldr'' }
True && (False && (True && True))
| { apply && }
False

Using foldr’’

foldr'' :: (a -> a -> a) -> a -> [a] -> a
foldr'' f v []     = v
foldr'' f v (x:xs) = x `f` foldr'' f v xs

foldr'' (||) False [True, False, True]
| { apply foldr'' }
True || foldr'' (||) False [False, True]
| { apply foldr'' }
True || (False || foldr'' (||) False [True])
| { apply foldr'' }
True || (False || (True || foldr'' (||) False []))
| { apply foldr'' }
True || (False || (True || False))
| { apply || }
True

The True Power of foldr’’

Can you write map' in terms of foldr''?

Try it!

foldr'' :: (a -> a -> a) -> a -> [a] -> a
foldr'' f v []     = v
foldr'' f v (x:xs) = x `f` foldr'' f v xs

map' :: (a -> b) -> [a] -> [b]
map' f xs = foldr'' ... ... ...

The True Power of foldr’’

This doesn’t work, why?

foldr'' :: (a -> a -> a) -> a -> [a] -> a
foldr'' f v []     = v
foldr'' f v (x:xs) = x `f` foldr'' f v xs

map' :: (a -> b) -> [a] -> [b]
map' f xs = foldr'' (\x rest -> f x : rest) [] xs
x    :: a
rest :: [b]

foldr'' :: (a -> a -> a) -> a -> [a] -> a

Our foldr'' function’s type is not general enough!

Exercise: foldr’’

Step 5: Generalize and simplify

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

This type allows a and b to be the same if desired or different

Now we can write map' in terms of foldr''

Exercise: foldr’’

Step 6: Test the function

> foldr'' (+) 0 [1,2,3]
6
> foldr'' (*) 1 [1,2,3]
6
> foldr'' (&&) True [True, False, True]
False
> foldr'' (||) False [True, False, True]
True

Exercise: filter

Try to write filter’ in terms of foldr

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

filter :: (a -> Bool) -> [a] -> [a]
filter pred [] = []
filter pred (x:xs)
    | pred x    = x : filter pred xs
    | otherwise = filter pred xs

filter' :: (a -> Bool) -> [a] -> [a]
filter' pred xs = foldr ... ... ...

Exercise: filter

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

filter :: (a -> Bool) -> [a] -> [a]
filter pred [] = []
filter pred (x:xs)
    | pred x    = x : filter pred xs
    | otherwise = filter pred xs

filter' :: (a -> Bool) -> [a] -> [a]
filter' pred xs = foldr go [] xs
  where
    go x rest
      | pred x    = x : rest
      | otherwise = rest

Understanding foldr

What is the first argument to the function give to foldr represent?

The head of the list

What is the second argument represent?

The recursive call to the tail

Just look at the definition of foldr:

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

Function Power

Can you write map in terms of filter or vice versa?

Try it!

You cannot!

This means map and filter are on the same level of power.

However, you can write map and filter with foldr so we say that foldr is more powerful

Example: length

length :: [a] -> Int
length xs = foldr (\x rest -> 1 + rest) 0 xs

Example: length

length :: [a] -> Int
length xs = foldr (\x rest -> 1 + rest) 0 xs

length [1,2,3]
| { apply foldr }
1 + foldr (\x rest -> 1 + rest) 0 [2,3]
| { apply foldr }
1 + (1 + foldr (\x rest -> 1 + rest) 0 [3])
| { apply foldr }
1 + (1 + (1 + foldr (\x rest -> 1 + rest) 0 []))
| { apply foldr }
1 + (1 + (1 + 0))
| { apply + }
3

Example: length

Another perspective

foldr (\x rest -> 1 + rest) 0 [1,2,3]
1 : (2 : (3 : [])) becomes
1 + (1 + (1 + 0))

foldr essentially replaces each cons (:) with the combining function f and the empty list with the base case v

More examples

foldr (+) 0 [1,2,3]
1 : (2 : (3 : [])) becomes
1 + (2 + (3 + 0))

foldr (*) 1 [1,2,3]
1 : (2 : (3 : [])) becomes
1 * (2 * (3 * 1))

foldr (&&) True [True, False, True]
True :  (False :  (True :  [])) becomes
True && (False && (True && True))

foldr (||) False [True, False, True]
True :  (False :  (True :  [])) becomes
True || (False || (True || False))

Exercise: reverse

Try to write reverse' in terms of foldr

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

reverse :: [a] -> [a]
reverse []     = []
reverse (x:xs) = reverse xs ++ [x]

reverse' :: [a] -> [a]
reverse' xs = foldr ... ... ...

Exercise: reverse

Try to write reverse' in terms of foldr

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

reverse :: [a] -> [a]
reverse []     = []
reverse (x:xs) = reverse xs ++ [x]

reverse' :: [a] -> [a]
reverse' xs = foldr go [] xs
  where
    go x rest = rest ++ [x]