1 / 29

Haskell. Chapter 5, Part II. Topics. Review/More Higher Order Functions Lambda functions Folds. Higher Order Functions. Higher-Order functions. applyTwice :: (a -> a) -> a -> a applyTwice f x = f (f x) (a -> a) is a function parentheses are needed.

I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.

- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript

Chapter 5, Part II

Topics
• Review/More Higher Order Functions
• Lambda functions
• Folds
Higher-Order functions

applyTwice :: (a -> a) -> a -> a

applyTwice f x = f (f x)

• (a -> a) is a function
• parentheses are needed.
• a is (of course) a type parameter, maybe Int, String, etc.
• BUT, parameter and result must have the same type
• Try:
• applyTwice(+3) 10
• applyTwice (++ " woot") "say"
• applyTwice (3:) [1]
• Note that we are passing partially applied functions (e.g., 3:, +3, etc.)
Example: zipWith

zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]

zipWith' _ [] _ = []

zipWith' _ _ [] = []

zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xsys

• joins two lists by applying function to corresponding elements
• must handle cases where lists are not equal length
• lists don’t need to have same type
• Try
• zipWith' (+) [4,2,5] [2,6,2]
• zipWith' (max) [4,2,5, 3] [2,6,2]
• zipWith' (++) ["foo ", "bar "] ["fighters", "bells"]
• zipWith' (*) (replicate 5 2) [1..] // replicates 2 5x
• zipWith' (zipWith' (*)) [[2,3],[4,6]] [[10,20],[100, 200]]
Example: flip

flip' :: (a->b->c) -> (b -> a -> c)

flip' f = g

where g x y = f y x

• Try:
• zip [1,2,3,4,5] "hello"
• flip' zip [1,2,3,4,5] "hello"
Lambda - l
• Anonymous function we use when that function is only needed once
• Typically use to pass to a higher-order function
• Syntax:
• \ (kind of like l)
• function parameters
• ->
• function body
• Example:

numLongChains :: Int

numLongChains = length (filter (\xs -> length xs > 15) (map chain [1..100]))

• compare to:

numLongChains :: Int

numLongChains = length (filter isLong (map chain [1..100]))

where isLongxs = length xs > 15

When not to use lambda
• Don’t use lambda when currying and partial application work… those are more readable
• Example, use:
• map (+3) [1,6,3,2]
• Not
• map (\x -> x + 3) [1,6,3,2]
• Both work... but which would you rather read??
More on lambda functions
• They can take multiple parameters

zipWith (\a b -> (a * 30 + 3) / b) [5,4,3,2,1] [1,2,3,4,5]

• Can include pattern matching
• BUT, only one pattern (can’t fall through as in normal functions)
• map (\(a, b) -> a + b) [(1,2),(3,4)]
Folds
• A programming language can make it quicker to write code if it includes language constructs that capture common patterns
• Think about common recursive pattern:
• Base case: empty list
• Pattern match x:xs
• Perform some action on x and (recursively) on xs
• In Haskell, this is what a fold does!
• Can be used whenever you want to traverse a list once and return something.
More details
• A fold takes:
• A binary function (e.g., +, div, etc.)
• A starting value (accumulator)
• A list to fold up

sum' :: (Num a) => [a] -> a

sum' xs = foldl (\acc x -> acc + x) 0 xs

• sum’ [2,4,5]
• 0 + 2
• 2 + 4
• 6 + 5
• 11

[2,4,5]

acc

0

fold

[4,5]

acc

2

fold

[5]

acc

6

fold

[]

acc

11

fold

Can use currying

sum'' :: (Num a) => [a] -> a

sum'' = foldl (+) 0

• *Main> sum'' [3,5]
• 8
• What happened to xs? The above returns a partially applied function that takes a list.
• In general, if have fn foo a = bar b a
• can rewrite as foo = bar b
• then call foo a
• Note that the definition is more concise without the lambda
Quick Exercise

sum could be done as a fold at the command line, e.g.,

• *Main>foldl (+) 0 [3,4,5]
• 12

EXERCISE

• Use a fold1 to create the product of the numbers in a list (just do this at the GHCi prompt, no function definition)
• Use a foldl to append strings stored in a list to an initial string of “Hello ”
• Use a foldl to subtract a list of numbers from an initial value (could be subtracting purchases from your wallet, for example)
Right folds
• foldr is like foldl, except it “eats up” the values starting from the right.
• In some cases, the result is the same.
• *Main>foldl (+) 0 [3,4,5]
• 12
• *Main>foldr (+) 0 [3,4,5]
• 12

[2,4,5]

acc

0

fold

[2, 4]

acc

5

fold

[2]

acc

9

fold

[]

acc

11

fold

Right folds

The accumulator value of a fold can be any type – including a list.

• *Main>foldr (\x acc -> (^2) x:acc) [] [2,3,4]
• [4,9,16]
• Note that the order of the arguments is reversed from the order of the parameters (x accparameters, [] [2,3,4] arguments)

If arguments not reversed:

• *Main>foldr (\x acc -> (^2) x:acc) [2,3,4] []
• [2,3,4]
• (nothing to “eat up” so result=acc)

[2,3, 4]

acc

[]

fold

[2, 3]

acc

[16]

fold

[2]

acc

[9, 16]

fold

[]

acc

[4,9,16]

fold

Can I trace this?
• scanl and scanr (and scanl1, scanr1) are like foldl and foldr, except they report intermediate accumulator states.
• Used to monitor the progress of a function that can be implemented as a fold.
• *Main>foldl (+) 0 [3,5,2,1]
• 11
• *Main> scanl (+) 0 [3,5,2,1]
• [0,3,8,10,11]
• *Main>scanr (\x acc -> (^2) x:acc) [] [2,3,4]
• [[4,9,16],[9,16],[16],[]]
• *Main>scanr (\x acc -> (^2) x:acc) [2,3,4] []
• [[2,3,4]]
Right folds – to implement map
• Like what we just did
• foldr(\x acc -> (^2) x:acc) [] [2,3,4]
• BUTuse function passed as argument rather than ^2

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

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

• map' (+3) [1,2,3]
• 3 : [ ]
• 2 : [3]
• 1 : [2,3]
• [1,2,3]
Which to use?
• Could have done map with left fold:

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

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

• Note that ++ is slower than :
• (why would that make sense?)
• SO, map' will be faster than map''
Another example – with Bool acc

elem' :: (Eq a) => a -> [a] -> Bool

elem' y ys = foldr (\x acc -> if x == y then True else acc) False ys

• Note that accumulator starts with False
• This code will work with an empty list

(we’ll do another one in a minute)

Two more folds
• foldr1 and foldl1
• Like foldr and foldl, but first (or last) element of the list is the starting value
• Can’t be called with empty list
• *Main> foldl1 (+) [2,3,4]
• 9

maximum' :: (Ord a) => [a] -> a

maximum' = foldl1 max

More fold examples

reverse' :: [a] -> [a]

reverse' = foldl (\acc x -> x : acc) []

• OR

reverse'' :: [a] -> [a]

reverse'' = foldl (flip (:)) []

• Quick exercise:
• Trace reverse'' [1,2,3]
• Remember: flip f x y = f y x
More fold examples

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

filter' p = foldr (\x acc -> if p x then x : acc else acc) []

last' :: [a] -> a

last' = foldl1 (\_ x -> x)

Another look at folds
• Can view as successive applications of some function to elements in a list
• Assume right fold, binary function f, starting acc z
• do foldron [3,4,5,6]
• this is essentially
• f 3 (f 4 (f 5 ( f 6 z)))
• If f is + and starting value is 0, this is:
• 3 + (4 + (5 + (6 + 0)))
Folds and infinite lists
• && returns True if all elements are True, False if any element is False
• So as soon as a False is encountered, the result is False

and' :: [Bool] -> Bool

and' xs = foldr (&&) True xs

• and [True, False, True]
• True && (False && (True && True))
Using and with infinite list
• repeat False
• False && (False && (False && (False ….
• Haskell is lazy. Only generates items as needed.
• && returns False if one of its parameters is False.

(&&) :: Bool -> Bool -> Bool

True && x = x

False && _ = False

• *Main> and' (repeat False)
• False
• Foldr works with infinite lists IF binary function doesn’t always evaluate its second parameter (as in &&). Will not work with infinite lists if second parameter is always needed.
Play and Share – higher order functions
• Write a function divisibleBy such that: divisibleBy 2 4 returns True, divisibleBy 2 5 returns False
• Try: map (divisibleBy 2) [2,3,4]
• Write a function divisibleByFive that returns a partially applied divisibleBy function
• Try: map divisibleByFive [2,4,5]
• Write isDivisibleByFive that uses a lambda function with map to achieve the same result (e.g., returns [False, False, True] for [2,4,5])

Suggested by a former student:

• Create a higher-order function named integrate that takes a function, range, and step size and computes approximate numerical integration by evaluating the function at each step.
• *Main> integrate square 2 4 0.001
• 18.66066700000209
• *Main> integrate cube 2 4 0.001
• 59.97200299999252