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
