Higher Order FunctionsChapter 11 of Thompson
http://learnyouahaskell.com/higher-order-functions
A function that takes another function as input, or returns a function asoutput, is called a higher-order function.
Mastering higher order functions will make you a more productiveprogrammer.
Higher order functions
A first example
We met the parametric polymorphic identity function:
id :: a -> aid x = x
Function types are just like any other type, so…
id :: (Int -> Char) -> (Int -> Char)
Reminder about the associativity of ->
(Int -> Char) -> (Int -> Char)
Could be written as
(Int -> Char) -> Int -> Char
But not as Int -> Char -> (Int -> Char)or Int -> Char -> Int -> Char
You have already seen many functions in this course with functions asoutput – any function with more than one ‘input’!
f :: Int -> (String -> Char)
• Two inputs (an Int and a String), returning a Char ✓• One input (an Int), returning a function of type String -> Char ✓
The second point of view is called partial application.
Functions as output
multiply :: Int -> Int -> Intmultiply x y = x * y
multiply :: Int -> Int -> Intmultiply x y = x * y
multiply 2 3
26
3
multiply :: Int -> (Int -> Int)(multiply x) y = x * y
(multiply 2) 3
26
3
multiply :: Int -> Int -> Intmultiply x y = x * y
multiply 2 is a function Int -> Int that doubles its input!
2
Partial application
So just asmultiply :: Int -> Int -> Int
is convenient shorthand formultiply :: Int -> (Int -> Int)
We havemultiply 2 3
as convenient shorthand for(multiply 2) 3
In general:
f e1 e2 … ekt1 -> t2 -> ... tn -> t
are shorthands for:
((...((f e1) e2)...) ek)t1 -> (t2 -> (...(tn -> t)...))
i.e., function application is left-associative-> is right-associative
Functions as input
applyTwice:: (a -> a) -> a -> aapplyTwice f x = f (f x)
> applyTwice sqrt 162.0> applyTwice (+3) 1016
Functions as input
applyTwice:: (a -> a) -> a -> aapplyTwice f x = f (f x)
> applyTwice (++ " HAHA") "HEY""HEY HAHA HAHA”> applyTwice ("HAHA " ++) "HEY""HAHA HAHA HEY"> applyTwice (3:) [1][3,3,1]
Functions as input and output – Composition
Recall from the very first week that, given functionsf :: a -> b and g :: b -> c
We can define their composition, a functiong . f :: a -> c
So composition itself is a higher order function, with functions as both inputs and output.
Composition
(.) :: (b -> c) -> (a -> b) -> (a -> c)(.) g f x = g (f x)
We usually write (.) g f infix as g . f
> ((+1) . (*2)) 13> ((*2) . (+1)) 14
We have built-in functionseven :: Int -> Boolnot :: Bool -> Bool
Assume we want to definemyOdd :: Int -> BoolmyOdd x = not (even x)
more succinctly:
myOdd’ = not . even
Fold (also called “reduce”) functions
Often we wish to traverse a list once, element by element, building up an output as we go.
This is called a fold.
There are ‘right folds’ and ‘left folds’; we will try to understand right folds before we look at the left.
These are the most useful higher order function and are worth understanding – they will make you a more productive programmer!
Folds
Often we wish to traverse a list once, element by element, building up an output as we go.
• Add each number in a list to get the sum of all the elements;• Multiply each number in a list to get the product of all the elements;• Increment a number by 1 for each element to get the list’s length;• Turn a list of strings into an acronym;• Map a function over a list element by element;• etc…
mySum :: [Int] -> IntmySum list = case list of[] -> 0x:xs -> x + mySum xs
myLen :: [a] -> IntmyLen list = case list of[] -> 0x:xs -> 1 + myLen xs
myProd :: [Int] -> IntmyProd list = case list of[] -> 1x:xs -> x * myProd xs
acro :: [String] -> Stringacro list = case list of[] -> ""x:xs -> head x : acro xs
How are these definitions different? How are they the same?
mySum :: [Int] -> IntmySum list = case list of[] -> 0x:xs -> x + mySum xs
myLen :: [a] -> IntmyLen list = case list of[] -> 0x:xs -> 1 + myLen xs
myProd :: [Int] -> IntmyProd list = case list of[] -> 1x:xs -> x * myProd xs
acro :: [String] -> Stringacro list = case list of[] -> ""x:xs -> head x : acro xs
The names are different – but Haskell doesn’t really care about that
mySum :: [Int] -> IntmySum list = case list of[] -> 0x:xs -> x + mySum xs
myLen :: [a] -> IntmyLen list = case list of[] -> 0x:xs -> 1 + myLen xs
myProd :: [Int] -> IntmyProd list = case list of[] -> 1x:xs -> x * myProd xs
acro :: [String] -> Stringacro list = case list of[] -> ""x:xs -> head x : acro xs
The types are different – suggests folds are polymorphic
mySum :: [Int] -> IntmySum list = case list of[] -> 0x:xs -> x + mySum xs
myLen :: [a] -> IntmyLen list = case list of[] -> 0x:xs -> 1 + myLen xs
myProd :: [Int] -> IntmyProd list = case list of[] -> 1x:xs -> x * myProd xs
acro :: [String] -> Stringacro list = case list of[] -> ""x:xs -> head x : acro xs
The base cases are different
mySum :: [Int] -> IntmySum list = case list of[] -> 0x:xs -> x + mySum xs
myLen :: [a] -> IntmyLen list = case list of[] -> 0x:xs -> 1 + myLen xs
myProd :: [Int] -> IntmyProd list = case list of[] -> 1x:xs -> x * myProd xs
acro :: [String] -> Stringacro list = case list of[] -> ""x:xs -> head x : acro xs
The step cases are different – but only a little! – different recipes to combine the head with the recursive call on the tail
mySum :: [Int] -> IntmySum list = case list of[] -> 0x:xs -> x + mySum xs
myLen :: [a] -> IntmyLen list = case list of[] -> 0x:xs -> 1 + myLen xs
myProd :: [Int] -> IntmyProd list = case list of[] -> 1x:xs -> x * myProd xs
acro :: [String] -> Stringacro list = case list of[] -> ""x:xs -> head x : acro xs
Everything else is the same!
Fold Right
This suggests that we could define a polymorphic higher order function that takes as input• a base case;• a recipe for combining the head with a recursive call on the tailand then does all of the rest of the work for us!
No more time-consuming error-prone recursions to write• Except for all the ones that don’t follow the format of the previous slide
foldr
foldr in action
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr :: (Int -> Int -> Int) -> Int -> [Int] -> Int
mySum = foldr (+) 0
myProd = foldr (*) 1
foldr in action
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr :: (a -> Int -> Int) -> Int -> [a] -> Int
myLen = foldr (\x y -> y + 1) 0
Or, to avoid a warning:
myLen = foldr (\_ y -> y + 1) 0
foldr in action
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr :: (String -> String -> String) -> String -> [String] -> String
acro = foldr (\x y -> head x : y) ""
How might you write a ‘safe’ version of acro that ignores empty strings instead of crashing on them?
foldr in action
foldr is just another function, and can be used as a helper anywhere:
myMaximum :: [Int] -> IntmyMaximum list = case list of[] -> error "No maximum of an empty list"x:xs -> foldr max x xs
Take some time to understand all the types here!
Defining foldr
myFoldr :: (a -> b -> b) -> b -> [a] -> bmyFoldr combine base list = case list of[] -> basex:xs -> combine x (myFoldr combine base xs)
Why is it fold right?
foldr (+) 0 [1,2,3]= 1 + foldr (+) 0 [2,3]= 1 + (2 + foldr (+) 0 [3])= 1 + (2 + (3 + foldr (+) 0 []))= 1 + (2 + (3 + 0))
So the combining operation associates to the right.• i.e. start with the base case, combine it with the rightmost element of list,
then continue until we reach the leftmost element of the list.
foldr and the structure of lists
A list [1,2,3]is really 1 : (2 : (3 : []) )
foldr replaces [] with a base case and : with a combining function
e.g. 1 + (2 + (3 + 0) )
So folding right is very natural because lists themselves associate right
Left folds
mySuml :: [Int] -> IntmySuml = mySumAcc 0wheremySumAcc acc list = case list of[] -> accx:xs -> mySumAcc (acc + x) xs
In the above, mySumAcc has type Int -> [Int] -> Int
Left folds
mySuml [1,2,3]= mySumAcc 0 [1,2,3]= mySumAcc (0 + 1) [2,3]= mySumAcc ((0 + 1) + 2) [3]= mySumAcc (((0 + 1) + 2) + 3) []= (((0 + 1) + 2) + 3)
No difference for +, but not all combining operations are associative!
It folds the list up from the left side
Defining foldl
myFoldl :: (b -> a -> b) -> b -> [a] -> bmyFoldl combine acc list = case list of[] -> accx:xs -> myFoldl combine (combine acc x) xs
Compare to the final line of the right fold:
x:xs -> combine x (myFoldr combine base xs)
foldl, folding left
foldl (+) 0 [1,2,3]= foldl (+) (0 + 1) [2,3]= foldl (+) ((0 + 1) + 2) [3]= foldl (+) (((0 + 1) + 2) + 3) []= (((0 + 1) + 2) + 3)
So the combining operation associates to the left.• i.e. start with the accumulator, combine it with the leftmost element of list,
then continue until we reach the empty list and return the accumulator.
foldr versus foldl
An example where they give different answers:
> foldr (-) 0 [1,2,3]2
> foldl (-) 0 [1,2,3]-6
Because ((0 – 1) – 2) – 3 ≠ 1 – (2 – (3 – 0))
foldr versus foldl
More generally, if we think of lists as built up from the empty list by using cons repeatedly, then lists are constructed from the right.
Therefore foldr tends to follow the way lists are constructed.
e.g. foldr (:) [] :: [a] -> [a] is the identity!
foldl goes in the reverse direction from the list’s construction• What happens if you use (:) with foldl?