290 likes | 550 Views
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.
E N D
Haskell 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 Trace with your partner (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