H. Conrad Cunningham
6 October 2017
Acknowledgements: These slides accompany Sections 5.3 “Haskell Function Concepts and Notation”, Section 5.4 “Additional Higher Order List Functions”, Section 5.5 “Rational Arithmetic Revisited”, and Section 5.6 “Merge Sort from Chapter 5”Higher-Order Functions" of the in-work textbook “Introduction to Functional Programming Using Haskell”.
Advisory: The HTML version of this document may require use of a browser that supports the display of MathML. A good choice as of October 2017 is a recent version of Firefox from Mozilla.
Code: The Haskell module for this chapter is in file Mod05HigherOrder.hs
.
Explore additional function concepts useful in conjunction with higher-order functions (strictness, currying, partial application, operator sections, combinators, lambda expressions, application operator, eager evaluation)
Demonstrate function composition, point-free style, and function pipelines
Examine additional higher-order function examples
Continue to reinforce how to think like functional programmer
Cannot reduce some expressions to simple values – div 1 0
triggers exception
Represent such undefined “values” with symbol (“bottom”)
Cannot represent , but can apply function to it
Call f
strict if f
Call f
nonstrict otherwise
Must evaluate strict argument before function can be; might not need to evaluate nonstrict
two :: a -> Int
two x = 2 -- argument not evaluated
reverse' :: [a] -> [a]n -- reverse
reverse' xs = rev xs []
where rev [] ys = ys
rev (x:xs) ys = rev xs (x:ys)
two (div 1 0) == 2
– nonstrict in argument
Arithmetic operations (e.g., +
) strict in both
reverse'
strict in its one argument
(++) :: [a] -> [a] -> [a]
[] ++ xs = xs -- 2nd arg not evaluated
(x:xs) ++ ys = x:(xs ++ ys)
(&&), (||) :: Bool -> Bool -> Bool
False && x = False -- 2nd arg not evaluated
True && x = x
False || x = x -- 2nd arg not evaluated
True || x = True
++
is strict first argument, nonstrict in second
&&
and ||
strict in first argument, nonstrict in second
add :: (Int,Int) -> Int
add (x,y) = x + y
add' :: Int -> (Int -> Int)
add' x y = x + y
add (x,y) = add’ x y
, but functions different types
What result of add 3
? – compiler error
What result of add’ 3
? – single argument function
What result of (add’ 3) 4
?
add’ x y = ((add’ x) y)
– binds left, tightly
currying – replace function that takes tuple by function that take arguments one at time
add’
similar to function (+)
((+) 3)
– (+)
partially applied
doublePos3 :: [Int] -> [Int]
doublePos3 xs = map' ((*) 2) (filter' ((<) 0) xs)
f
and g
extensionally equal if f x = g x
for all x
f, g :: a -> a
f x = some_expression
g x = f x -- one defintion
g = f -- equivalent definition
((<) 0)
confusing because <
normally infix – ((<) 0)
returns True
for positive integers
((/) 2)
divides 2 by its operand – what about function that takes half
flip' :: (a -> b -> c) -> b -> a -> c -- flip in Prelude
flip' f x y = f y x
(flip (/) 2)
is halving operatorFor infix operator and expression e
:
(e
)
represents ((
) e)
(
e)
represents (flip (
) e)
(1+)
is successor function
(0<)
is test for positive integer
(/2)
is halving function.
(1.0/)
is reciprocal function
(:[])
returns singleton list containing argument
sumCubes :: [Int] -> Int
sumCubes xs = sum' (map' (^3) xs)
sum' = foldl' (+) 0 -- sum
function without any free variables
flip' :: (a -> b -> c) -> b -> a -> c -- flip in Prelude
flip' f x y = f y x -- C combinator
const' :: a -> b -> a -- const in Prelude
const' k x = k -- constant function construtor
-- K combinator
id' :: a -> a -- id in Prelude
id' x = x -- identity function
-- I combinator
sum (map (const 1) xs)
do? fst' :: (a,b) -> a -- fst in Prelude
fst' (x,_) = x
snd' :: (a,b) -> b -- snd in Prelude
snd' (_,y) = y
fst3
, snd3
, thd3
extract 1st, 2nd, 3rd components of 3-tuples reverse' :: [a] -> [a] -- reverse in Prelude
reverse' = foldlX (flip' (:)) [] -- foldl, flip
flip (:)
takes list on left and element on right
Starts with []
from left
Attaches each element as head of list
curry' :: ((a, b) -> c) -> a -> b -> c -- curry in Prelude
curry' f x y = f (x, y)
uncurry' :: (a -> b -> c) -> ((a, b) -> c) -- uncurry in Prelude
uncurry' f p = f (fst p) (snd p)
fork :: (a -> b, a -> c) -> a -> (b,c)
fork (f,g) x = (f x, g x)
cross :: (a -> b, c -> d) -> (a,c) -> (b,d)
cross (f,g) (x,y) = (f x, g y)
Not in Prelude
fork
applies each pair of functions separately to value to create pair of results
cross
applies pair of functions component-wise to pair of values to create pair of results
infixr 9 .
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)
Combines several “smaller” functions into “larger” functions
Binds from right, highest precedence except function application
f . (g . h) = (f . g) . h -- associative
id . f = f . id -- "id" is identity
Pointful style – gives parameters explicitly
doit x = f1 (f2 (f3 (f4 x)))
Point-free style – leaves parameters implicit
doit = f1 . f2 . f3 . f4
count :: Int -> [[a]] -> Int
count n -- point-free expression below defines pipeline
| n >= 0 = length' . filter' (== n) . map' length'
| otherwise = const' 0 -- discard 2nd arg, return 0
Pipeline takes polymorphic list of lists as input
map' length'
replaces each inner list by its length
filter' (== n)
keeps only elements equal to n
length'
determines how many elements remaining
Pipeline outputs number of lists within input with length n
Point-free expressions define function pipelines
Partial applications, operator sections, and combinators are plumbing for pipelines
Key concepts in thinking like functional programmers
doublePos3 xs = map' ((*) 2) (filter' ((<) 0) xs)
Above re-expressed in point-free style below
doublePos4 :: [Int] -> [Int]
doublePos4 = map' (2*) . filter' (0<)
last' = head . reverse' -- last in Prelude
init' = reverse' . tail . reverse' -- init in Prelude
“Quick and dirty” function pipeline more efficient if implemented directly
last2 :: [a] -> a -- last in Prelude
last2 [x] = x
last2 (_:xs) = last2 xs
init2 :: [a] -> [a] -- init in Prelude
init2 [x] = []
init2 (x:xs) = x : init2 xs
any', all' :: (a -> Bool) -> [a] -> Bool
any' p = or' . map' p -- any in Prelude
all' p = and' . map' p -- all in Prelude
elem', notElem' :: Eq a => a -> [a] -> Bool
elem' = any' . (==) -- elem in Prelude
notElem' = all' . (/=) -- notElem in Prelude
elem’ x xs |
|
{ expand elem’ } |
|
(any’ . (==)) x xs |
|
{ expand composition } | |
any’ ((==) x) xs |
squareAll2 :: [Int] -> [Int]
squareAll2 xs = map' sq xs
where sq x = x * x
Above from earlier can be re-expressed in point-free style
squareAll3 :: [Int] -> [Int]
squareAll3 = map' (\x -> x * x)
(\x -> x * x)
is lambda expression (anonymous function)
Syntax: \
atomicPatterns ->
expression
(\x y -> (x+y)/2)
averages two numbers
(\n _ -> n+1)
takes integer “counter” and value, returns incremented “counter”
import Data.List ( foldl' )
length4 :: [a] -> Int -- length in Prelude
length4 = foldl' (\n _ -> n+1) 0
$
(1)Function application associates to left with highest binding power
w + f x y * z
w + (((f x) y) * z) -- same as above
Sometimes want it to associate to right with lowest binding power
infixr 0 $
($) :: (a -> b) -> a -> b
f $ x = f x
$
(2)For one-argument functions f
, g
, and h
f $ g $ h $ z + 7
(f (g (h (z+7)))) -- same as above
(f . g . h) (z+7) -- same as above
For two-argument functions f'
, g'
, and `h’
f' w $ g' x $ h' y $ z + 7
((f' w) ((g' x) ((h' y) (z+7)))) -- same as above
(f' w . g' x . h' y) (z+7)
$
(3) foldr (+) 0 $ map (2*) $ filter odd $ enumFromTo 1 20
Prelude function enumFromTo m n
generates inclusive sequence of integers from m
to n
[m..n]
syntactic sugar for enumFromTo m n
(Chapter 7)
Haskell lazily evaluated – if argument nonstrict, may never be evaluated
Compiler’s strictness analysis can safely force eager evaluation as optimization, preserving semantics
GHC’s -O
option enables code optimization, including strictness analysis
Programmer can force eager evaluation explicitly
seq
(1) seq :: a -> b -> b
x `seq` y = y
Returns second argument y
But as side effect evaluates x
(to head normal form)
Evaluated until outer layer of structure such as (h:t)
revealed, but h
and t
may not be fully evaluated
seq
(2) foldlP :: (a -> b -> a) -> a -> [b] -> a -- foldl' in Data.List
foldlP f z [] = z -- optimized
foldlP f z (x:xs) = y `seq` foldl' f y xs
where y = f z x
Evaluates z
argument of tail recursive foldlP
eagerly
Uses where
to do graph reduction of y
$!
(1) infixr 0 $!
($!) :: (a -> b) -> a -> b
f $! x = x `seq` f x
f $! x
eagerly evaluates argument x
before applying function f
foldlQ :: (a -> b -> a) -> a -> [b] -> a -- foldl' in Data.List
foldlQ f z [] = z -- optimized
foldlQ f z (x:xs) = (foldlQ f $! f z x) xs
$!
(2) sum4 :: [Integer] -> Integer -- sum in Prelude
sum4 xs = sumIter xs 0 -- tail recursive
where sumIter [] acc = acc
sumIter (x:xs) acc = sumIter xs (acc+x)
sum5 :: [Integer] -> Integer -- sum in Prelude
sum5 xs = sumIter xs 0
where sumIter [] acc = acc
sumIter (x:xs) acc = sumIter xs $! acc + x
Be careful!
seq
and $!
change semantics of lazily evaluated language where argument nonstrict
May cause program to terminate abnormally or evaluate unneeded expressions
takeWhile':: (a -> Bool) -> [a] -> [a] -- takeWhile in Prelude
takeWhile' p [] = []
takeWhile' p (x:xs)
| p x = x : takeWhile' p xs
| otherwise = []
dropWhile' :: (a -> Bool) -> [a] -> [a] -- dropWhile in Prelude
dropWhile' p [] = []
dropWhile' p xs@(x:xs')
| p x = dropWhile' p xs'
| otherwise = xs
Remove blank spaces from beginning of string
dropWhile ((==) ' ')
For all finite lists xs
and predicates p
on same type:
takeWhile p xs ++ dropWhile p xs = xs
span' :: (a -> Bool) -> [a] -> ([a],[a]) -- span in Prelude
span' _ xs@[] = (xs, xs)
span' p xs@(x:xs')
| p x = let (ys,zs) = span' p xs' in (x:ys,zs)
| otherwise = ([],xs)
break' :: (a -> Bool) -> [a] -> ([a],[a]) -- break in Prelude
break' p = span (not . p)
For all finite lists xs
and predicates p
on same type:
span p xs == (takeWhile p xs, dropWhile p xs)
zipWith' :: (a->b->c) -> [a]->[b]->[c] -- zipWith in Prelude
zipWith' z (x:xs) (y:ys) = z x y : zipWith' z xs ys
zipWith' _ _ _ = []
zip'' :: [a] -> [b] -> [(a,b)] -- zip
zip'' = zipWith' (\x y -> (x,y))
zip''' = zipWith (,) -- same as zip''
Scalar product of vectors
sp :: Num a => [a] -> [a] -> a
sp xs ys = sum' (zipWith' (*) xs ys)
zipWith3
zipWith7
for 3- to 7-tuples eqRat :: Rat -> Rat -> Bool
eqRat x y = (numer x) * (denom y) == (numer y) * (denom x)
Above can be reimplemented/extended using higher order functions
compareRat :: (Int -> Int -> Bool) -> Rat -> Rat -> Bool
compareRat r x y = r (numer x * denom y) (denom x * numer y)
eqRat,neqRat,ltRat,leqRat,gtRat,geqRat :: Rat -> Rat -> Bool
eqRat = compareRat (==)
neqRat = compareRat (/=)
ltRat = compareRat (<)
leqRat = compareRat (<=)
gtRat = compareRat (>)
geqRat = compareRat (>=)
If list fewer than two elements, sorted
Otherwise split into two half-length sublists, sort each
Merge two ascending sublists into ascending list
msort :: Ord a => (a -> a -> Bool) -> [a] -> [a]
msort _ [] = []
msort less xs = merge (msort less ls) (msort less rs)
where n = (length xs) `div` 2
(ls,rs) = splitAt n xs
merge [] ys = ys
merge xs [] = x
merge ls@(x:xs) rs@(y:ys)
| less x y = x : merge xs rs
| otherwise = y : merge ls ys
msort :: Ord a => (a -> a -> Bool) -> [a] -> [a]
msort _ [] = []
msort less xs = merge (msort less ls) (msort less rs)
where n = (length xs) `div` 2
(ls,rs) = splitAt n xs
merge [] ys = ys
merge xs [] = x
merge ls@(x:xs) rs@(y:ys)
| less x y = x : merge xs rs
| otherwise = y : merge ls ys
Under what circumstances does msort less xs
terminate normally?
What is time complexity of msort less xs
? space complexity?
Function concepts useful in conjunction with higher-order functions (strictness, currying, partial application, operator sections, combinators, lambda expressions, application operator, eager evaluation, etc.)
Function composition, point-free style, and function pipelines
The Haskell code for this section is in file Mod05HigherOrder.hs
.