CSci 450/503: Programming Languages

Haskell Function Concepts

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.

Haskell Function Concepts

Lecture Goals

Strictness (1)

Strictness (2)

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)

Strictness (3)

(++) :: [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

Currying and Partial Application (1)

add :: (Int,Int) -> Int
add (x,y) = x + y
add' :: Int -> (Int -> Int)
add' x y = x + y

Currying and Partial Application (2)

doublePos3 :: [Int] -> [Int]
doublePos3 xs = map' ((*) 2) (filter' ((<) 0) xs)

Property of Extensionality

f, g :: a -> a
f x = some_expression
g x = f x -- one defintion
g = f -- equivalent definition

Operator Section Motivation

flip' :: (a -> b -> c) -> b -> a -> c -- flip in Prelude
flip' f x y = f y x

Operator Section Definition

Operator section (syntactic sugar):

For infix operator \oplus and expression e:

  • (e\oplus)represents ((\oplus) e)

  • (\oplus e) represents (flip (\oplus) e)

Operator Section Examples

sumCubes :: [Int] -> Int
sumCubes xs = sum' (map' (^3) xs)
sum' = foldl' (+) 0 -- sum

Combinator Definition

Combinator:

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

Tuple Selector Combinators

fst' :: (a,b) -> a -- fst in Prelude
fst' (x,_) = x
snd' :: (a,b) -> b -- snd in Prelude
snd' (_,y) = y

Reverse Using Combinator

reverse' :: [a] -> [a] -- reverse in Prelude
reverse' = foldlX (flip' (:)) [] -- foldl, flip

Curry and Uncurry Combinators

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 and Cross Combinators

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)

Functional Composition Definition

infixr 9 .
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)
f . (g . h) = (f . g) . h -- associative
id . f = f . id -- "id" is identity

Pointful and Point-Free Styles

Pointful style – gives parameters explicitly

doit x = f1 (f2 (f3 (f4 x)))

Point-free style – leaves parameters implicit

doit = f1 . f2 . f3 . f4

Function Pipelines (1)

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

Function Pipelines (2)

doublePos3 xs = map' ((*) 2) (filter' ((<) 0) xs)

Above re-expressed in point-free style below

doublePos4 :: [Int] -> [Int]
doublePos4 = map' (2*) . filter' (0<)

Function Pipelines (3)

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

Function Pipelines (4)

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
\Longrightarrow { expand elem’ }
(any’ . (==)) x xs
\Longrightarrow { expand composition }
any’ ((==) x) xs

Lambda Expression Motivation

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)

Lambda Expression Examples

import Data.List ( foldl' )
length4 :: [a] -> Int -- length in Prelude
length4 = foldl' (\n _ -> n+1) 0

Application Operator $ (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

Application Operator $ (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)

Application Operator $ (3)

foldr (+) 0 $ map (2*) $ filter odd $ enumFromTo 1 20

Eager Evaluation

Eager Evaluation Using seq (1)

seq :: a -> b -> b
x `seq` y = y

Eager Evaluation Using 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

Eager Evaluation Using $! (1)

infixr 0 $!
($!) :: (a -> b) -> a -> b
f $! x = x `seq` f x
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

Eager Evaluation Using $! (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

Eager Evaluation Caution

List-Breaking Operations (1)

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

List-Breaking Operations (2)

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

List-Breaking Operations (3)

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)

List-Combining Operations

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)

Rational Arithmetic Revisited

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 (>=)

Merge Sort (1)

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

Merge Sort (2)

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

Key Ideas

Code

The Haskell code for this section is in file Mod05HigherOrder.hs.