Haskell - Colorado School of Mines
Download
Report
Transcript Haskell - Colorado School of Mines
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.
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 xs ys
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
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 isLong xs = length xs > 15
When not to use lambda
Don’t use lambda when currying and partial application
work… those are more readable
Example, use:
Not
map (+3) [1,6,3,2]
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
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]
[2,4,5]
2+4
6+5
11
fold
acc
2
[4,5]
[5]
fold
0+2
fold
acc
0
acc
6
[]
fold
acc
11
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
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]
fold
acc
0
fold
acc
5
[2, 4]
[2]
fold
acc
9
[]
fold
acc
11
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 acc parameters, [] [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]
fold
acc
[]
fold
acc
[16]
[2, 3]
[2]
fold
acc
[9, 16]
[]
fold
acc
[4,9,16]
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]
BUT use function passed as argument rather than ^2
map' :: (a -> b) -> [a] -> [b]
map' f xs = 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
foldl vs foldr
Try these:
foldr (subtract) 0 [5,4,3]
scanr (subtract) 0 [5,4,3]
scanr (subtract) [5,4,3] 0 -- doesn’t work, why?
foldl (subtract) 0 [5,4,3]
scanl (subtract) 0 [5,4,3]
foldl (flip (subtract)) 0 [5,4,3]
foldl1 (subtract) [5,4,3]
scanl1 (flip (subtract)) [5,4,3]
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
Hint: use scanl if you’re stuck on this
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 foldr on [3,4,5,6]
this is essentially
f (f (f ( f z 6) 5) 4) 3
If f is + and starting value is 0, this is:
(((6 + 0) + 5) + 4) + 3 => 18
If f is subtract and starting value is 0, this is:
(((0 – 6) – 5) – 4) – 3 => -18
compare to foldl: 6 - (5 - (4 - (3 – 0))) => 2
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))
Try: scanr (&&) True [True, False]
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