H. Conrad Cunningham
18 September 2017
Acknowledgements: These slides accompany Chapter 4 “List Programming” from “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 September 2017 is a recent version of Firefox from Mozilla.
Introduce Haskell immutable lists and data sharing
Discuss polymorphism
Introduce first-order polymorphic list programming
Introduce infix operations and properties
Reinforce concepts: recursive programming strategies, preconditions, postconditions, termination, time and space complexity
not subject to change as program executes
Primitives: Int
, Integer
, Float
, Double
, Char
, Bool
Functions – chapter 5
Tuples – chapter 2
Lists (and String
) – this chapter
User-defined (algebraic) data types – chapter 8
Type [t]
sequence of zero or more values of type t
– algebraic data type supported by syntactic sugar
Hierarchical structure – either empty or pair of head element and tail list
Empty list []
– pronounced “nil”
“Cons” operator :
constructs list from head/tail such as 3:[]
notation that simplifies expression, but adds no new functionality
Equivalent expressions
5:(3:(2:[]))
5:3:2:[]
– “cons” binds from the right
[5,3,2]
– comma separated list literal
Constructor :
Selector head (h:t)
h
Selector tail (h:t)
t
Prelude library functions: e.g., length
, sum
, product
, take
, drop
, ++
, reverse
String
type synonym for [Char]
type String = [Char]
Syntactic sugar: "oxford"
same as [’o’,’x’,’f’,’o’,’r’,’d’]
"Hello\nworld!\n"
string with two newline characters
"\12\&3"
same as['\12','3']
Example function to compute length of string
len :: String -> Int
len s = if s == [] then 0 else 1 + len (tail s)
property of having “many shapes”
General length function for more types: use type variable
len :: [a] -> Int
Polymorphic type – think Java generic
head :: [a] -> a
tail :: [a] -> [a]
(:) :: a -> [a] -> [a]
Ad hoc polymorphism, same function name denotes different implementations depending on use
Overloading, compiler determines which to invoke, early binding
E.g., +
in Java, Haskell type classes
Subtyping, runtime searches hierarchy of types, late binding
Called just polymorphism in Java, does not exist in Haskell
Parametric polymorphism, same implementation used for different types
Called generics in Java, Haskell just polymorphism
Use pattern matching on lists
Follow the types to implementations
Let form of the data guide form of the algorithm
Consider two basic cases: empty and nonempty lists
Int
(1)Type signature: sum' :: [Int] -> Int
Sum of an empty list? sum' [] = 0
identity element
Sum of nonempty list? sum' (x:xs) = x + sum' xs
sum' :: [Int] -> Int -- sum in Prelude
sum' [] = 0 -- nil list
sum' (x:xs) = x + sum' xs -- non-nil list
sum' [2,4,6,8]
yields 2 + (4 + (6 + (8 + 0)))
Int
(2) sum' :: [Int] -> Int -- sum in Prelude
sum' [] = 0 -- nil list
sum' (x:xs) = x + sum' xs -- non-nil list
Termination of sum' xs
? (What if xs
infinite?)
Precondition? Postcondition?
Backward recursive? Tail recursive?
Time complexity? Space complexity?
Int
(3)Exercise: Write a variant of sum'
that uses a tail recursive auxiliary function.
Integer
(1) product' :: [Integer] -> Integer -- product in Prelude
product' [] = 1 -- nil list
product' (0:_) = 0 -- 0 at head
product' (x:xs) = x * product' xs -- non-nil, non-zero
Identity element
Zero element
product' [2,4,6,8]
yields 2 * (4 * (6 * (8 * 1)))
Integer
(2) product' :: [Integer] -> Integer -- product in Prelude
product' [] = 1 -- nil list
product' (0:_) = 0 -- 0 head, don't care
product' (x:xs) = x * product' xs -- non-nil, non-zero
Termination of product' xs
?
Precondition? Postcondition?
Backward recursive? Tail recursive?
Time complexity? Space complexity?
sum'
and product'
sum'
and product'
similar computational patterns – note!
different base types, but both numbers
both return identity element for nil
zero handled differently by product'
length' :: [a] -> Int -- length in Prelude
length' [] = 0 -- nil list
length' (_:xs) = 1 + length' xs -- non-nil list
Polymorphic list type
Don’t care about actual value – use _
Same correctness and complexity characteristics as sum'
Problem: Replace group of adjacent duplicate integers by one occurrence
What does adjacency mean?
fewer then two elements? keep everything
first two elements duplicates? remove one
first two elements not duplicates? keep both
more than two adjacent duplicates? step one element at time
Function examines first two elements
remdups :: [Int] -> [Int]
remdups (x:y:xs) -- note order of equations
| x == y = remdups (y:xs)
| x /= y = x : remdups (y:xs)
remdups xs = xs
Termination of remdups xs
?
Time complexity? Space complexity?
How can we make work for polymorphic list?
[a] -> [a]
?
Cannot compare everything for equality (e.g., functions)
Constrain to class Eq
, which support ==
and /=
remdups :: Eq a => [a] -> [a]
remdups (x:y:xs)
| x == y = remdups (y:xs)
| x /= y = x : remdups (y:xs)
remdups xs = xs
Class Ord
also useful, extend Eq
with ordered comparisons <
, <=
, >
>=
Pattern | Argument | Succeeds? | Bindings |
1 |
1 |
yes | none |
x |
1 |
yes | x 1 |
(x:y) |
[1,2] |
yes | x 1 , y [2] |
(x:y) |
[[1,2]] |
yes | x [1,2] , y [] |
(x:y) |
["olemiss"] |
yes | x "olemiss" , y [] |
(x:y) |
"olemiss" |
yes | x ’o’ , y "lemiss" |
Pattern | Argument | Succeeds? | Bindings |
(1:x) |
[1,2] |
yes | x [2] |
(1:x) |
[2,2] |
no | none |
(x:_:_:y) |
[1,2,3,4,5,6] |
yes | x 1 , y [4,5,6] |
[] |
[] |
yes | none |
[x] |
["Cy"] |
yes | x "Cy" |
[1,x] |
[1,2] |
yes | x 2 |
[x,y] |
[1] |
no | none |
(x,y) |
(1,2) |
yes | x 1 , y 2 |
xs = [1,2,3]
ys = 0:xs
zs = tail xs
For immutable xs
, neither ys
nor zs
require copying
Efficient immutable structures via data sharing – persistent
But head xs
and remdups xs
require copying
Generalize tail
to drop'
that removes first n
elements
drop' :: Int -> [a] -> [a] -- drop in Prelude
drop' n xs | n <= 0 = xs
drop' _ [] = []
drop' n (_:xs) = drop' (n-1) xs
drop 2 "oxford"
"ford"
Different approach than tail
to nil list – seems logical to return nil
Data sharing between argument and result
drop' :: Int -> [a] -> [a] -- drop in Prelude
drop' n xs | n <= 0 = xs
drop' _ [] = []
drop' n (_:xs) = drop' (n-1) xs
Termination of drop' n xs
? (note two base cases)
Tail recursive?
Time complexity? Space complexity?
Generalize head
to take'
that returns first n
elements
take' :: Int -> [a] -> [a] -- take in Prelude
take' n _ | n <= 0 = []
take' _ [] = []
take' n (x:xs) = x : take' (n-1) xs
take 2 "oxford"
"ox"
What returned when list nil?
Generalize head
to take
that returns first n
elements
take' :: Int -> [a] -> [a] -- take in Prelude
take' n _ | n <= 0 = []
take' _ [] = []
take' n (x:xs) = x : take' (n-1) xs
Termination of take' n xs
?
Data sharing?
Tail recursive?
Time complexity? Space complexity?
function of type t1 -> t2 -> t3
Often prefer infix syntax, e.g., x + y
Use parentheses to specify order ((y * (z+2)) + x)
)
Use precedence to avoid parentheses for different operators
x + y * z
same as (x + (y * z))
Use binding or association order to avoid parentheses for similar operators
left binding x + y - z
same as ((x + y) - z)
right binding x^y^z
same as (x^(y^z))
free binding means no default binding order
Haskell declarations for left, right, free binding with precedence 0-9, where higher number binds more tightly
infixl n op
infixr n op
infix n op
infixr 8 ^ -- exponentiation
infixl 7 * -- multiplication
infix 7 / -- division
infixl 6 +, - -- addition, subtraction
infixr 5 : -- cons
infix 4 ==, /=, <, <=, >=, > -- relational comparisons
infixr 3 && -- Boolean AND
infixr 2 || -- Boolean OR
Function application has precdence of 10
++
infixr 5 ++
(++) :: [a] -> [a] -> [a] -- in Prelude
[] ++ xs = xs -- nil left operand
(x:xs) ++ ys = x:(xs ++ ys) -- non-nil left operand
Termination of xs ++ ys
?
Precondition? Postcondition?
Time complexity? Space complexity?
Data sharing?
++
(1)++
:For any finite lists xs
, ys
, and zs
, xs ++ (ys ++ zs) == (xs ++ ys) ++ zs
++
:For any finite list xs
, [] ++ xs = xs = xs ++ []
++
(2)For all finite lists xs
:
sum (xs ++ ys) = sum xs + sum ys
product (xs ++ ys) = product xs * product ys
length (xs ++ ys) = length xs + length ys
For all natural numbers n
and finite lists xs
:
take n xs ++ drop n xs = xs
!!
infixl 9 !!
(!!) :: [a] -> Int -> a
xs !! n | n < 0 = error "!! negative index"
[] !! _ = error "!! index too large"
(x:_) !! 0 = x
(_:xs) !! n = xs !! (n-1)
Termination of xs !! n
?
Precondition? Postcondition?
Time complexity? Space complexity?
Tail recursive? Data sharing?
rev
(1) rev :: [a] -> [a]
rev [] = [] -- nil argument
rev (x:xs) = rev xs ++ [x] -- non-nil argument
Termination of rev xs
? (note ++
)
Precondition? Postcondition?
Tail recursive?
Time complexity? (careful!) Space complexity?
Data sharing?
rev
(2)For any finite lists xs
and ys
, rev (xs ++ ys) = rev ys ++ rev xs
For any finite list xs
, rev (rev xs) = xs
For any finite lists xs
and ys
and natural numbers n
,
take n (rev xs) = rev (drop (length xs - n) xs)
reverse
reverse' :: [a] -> [a]n -- reverse in Prelude
reverse' xs = rev xs []
where rev [] ys = ys
rev (x:xs) ys = rev xs (x:ys)
Termination of rev xs ys
?
Precondition? Postcondition?
Time complexity? Space complexity?
Compare ++
versus :
splitAt' :: Int -> [a] -> ([a],[a]) -- splitAt in Prelude
splitAt' n xs = (take' n xs, drop' n xs)
zip' :: [a] -> [b] -> [(a,b)] -- zip in Prelude
zip' (x:xs) (y:ys) = (x,y) : zip' xs ys -- zip.1
zip' _ _ = [] -- zip.1
unzip' :: [(a,b)] -> ([a],[b]) -- unzip in Prelude
unzip' [] = ([],[])
unzip' ((x,y):ps) = (x:xs, y:ys)
where (xs,ys) = unzip' ps
Every element is <=
all of its successors
To sort x:xs
, sort tail xs
, then insert x
at correct position
isort :: [Int] -> [Int]
isort [] = []
isort (x:xs) = insert x (isort xs)
insert :: Int -> [Int] -> [Int]
insert x [] = [x]
insert x xs@(y:ys) -- xs alias of (y:xs)
| x <= y = (x:xs)
| otherwise = y : (insert x ys)
Termination of insert x xs
? of isort xs
?
Time complexity of insert x xs
? of isort xs
?
Space complexity of insert x xs
? of isort xs
?
Polymorphic list, element constrained to class Ord
isort' :: Ord a => [a] -> [a]
isort' [] = []
isort' (x:xs) = insert' x (isort' xs)
insert' :: Ord a => a -> [a] -> [a]
insert' x [] = [x]
insert' x xs@(y:ys) -- xs alias of (y:xs)
| x <= y = (x:xs)
| otherwise = y : (insert' x ys)
Polymorphism – powerful generalization mechanism
Immutable lists and data sharing
Follow the types to implementations
Let form of the data guide form of the algorithm
Infix operations and properties
The Haskell code for this section is in file Mod04Lists.hs
.