Topics in Advanced Computing Lesson 3: Recursion / Higher order functions

  1. Questions
    1. Reminders
    2. Reading
  2. Finish up
  3. Recursion
    1. Examples
    2. Quicksort
    3. zip / zipWith
  4. Higher-order Functions
    1. Currying
    2. Partial Application
    3. Maps / filters
    4. Lambdas
    5. folds
    6. foldr
    7. foldl1 / foldr1
    8. scanl/scanr
    9. Application
    10. Composition

Questions

GH Classroom questions? Questions from last time?

Reminders

Reading

Finish up

Notes from last week that we didn’t get to:

Note: We probably won’t get through the rest of these notes in one class.

Recursion

Examples

How do we define these recursively?

Quicksort

The quicksort algorithm is a classical $O(n \log n)$ sorting algorithm. The idea is as follows:

How do we implement this in Haskell? Hint: use the head as the pivot.

quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x:xs) = 

Hints:

zip / zipWith

zip: take two lists and return a list of pairs.

zip [0, 1, 2] [1..]

Returns [(0,1), (1,2), (2,3)] (cuts off the infinite list!). Exercise: How would we define this using recursion / pattern matching? (Hint: base cases?)

zipWith: combination of zip and map. Take a function, two lists, and returns a list where we apply the function to both list elements.

zipWith (+) [0..10] [1..]

Returns [1,3,5,7,9,11,13,15,17,19,21].

Exercise: Can we define zipWith using zip? Can we define zipWith without using zip (recursion / pattern matching)?

Higher-order Functions

addNums :: Num a => a -> a -> a
addNums x y = x + y

This is called currying (named after Haskell Curry): translating a function of multiple arguments to a sequence of one-argument functions.

addNums is a function which takes in a parameter of typeclass Num, and returns another function. That returned function is one which takes in a parameter of typeclass Num and returns a value of typeclass Num.

Upshot: partial application:

addFour = addNums 4

Currying

Partial Application

Maps / filters

Map:

Filter: Take a predicate (a function which returns a Bool) and a list, and return the sublist of those items which the predicate evaluates to try

Example: filter (>0) [3, -1, 4, 7, -2, 0, 8] returns [3, 4, 7, 8].

Definition?

Other examples?

Lambdas

folds

Repeatedly apply a function to more and more of a list. Takes in a function, a starting value, and a list, and returns a single value.

Definition:

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

Ex: foldl (\acc x -> acc + x) 0 [1..5] is the sum function! (adds up the numbers in the list [1..5]). How?

In the prelude, in fact, sum is defined as sum = foldl (+) 0.

foldr

foldl is technically a “left-fold”. There is also a “right-fold”, foldr.

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

This is an implementation of the map function. This one takes the function f and the list, and, starting on the right, applies f to each element on the list and prepends it to the rest of the list. You could do this with foldl and ++, but : is much more efficient than ++ (why?).

(This is not how map is actually implemented! Can you think of a recursive, pattern-matching way to implement it instead?)

Lazy-evaluation:

quitZero x acc = if x == 0 then 0 else x+acc
foldr quitZero 0 [3,2,1,0,100,undefined]

What happens here? What about for foldr quitZero 0 [3,2,1,100,undefined]? Lazy evaluation!

foldl1 / foldr1

scanl/scanr

foldl / foldr but returns a list of all partially accumulated values.

Application

The ‘$’ operator is the “function application” operator. Why would we use it? It changes precedence.

f a b c binds as (((f a) b) c), ie apply f to a, then take that result and apply it to b, then apply that result to c.

f $ a b c means apply a to b, apply that result to c, and then use that result as the argument to f.

Example:

length (takeWhile (<1000) (scanl1 (+) (map sqrt[1..])))
length $ takeWhile (<1000) $ scanl1 (+) $ map sqrt[1..]

Those do the same thing, second is a bit more readable.

Composition

Using the standard prelude functions negate and abs, let’s take a list of numbers and make all of the values negative.

map \x -> negate(abs x) [5, -3, 7, -2]

Cleaner if we use the . operator for composition:

map (negate . abs) [5, -3, 7, -2]

“Compose the negate and abs functions, pass that to map”.