Part3 Notes

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 12

Introduction to Functional Programming

Using Lists

Peter Jeavons Michaelmas Term 2011

1
1.1

Standard functions on lists


map, lter, concat, etc.

The map function Its often useful to apply a function to every element of a list. The standard function map does this. > map :: (a -> b) -> [a] -> [b] > map f [] = [] > map f (x:xs) = f x : map f xs For example: map square [1,2,3,4] = [1,4,9,16] Note that map is a higher order function. Some laws for map The function map satises a number of laws; for example: (map f . map g) xs = map (f . g) xs Laws like these can be used to transform programs into more ecient versions. Removing arguments (map f . map g) xs = map (f . g) xs Fact: if two functions always return the same result when given the same arguments, then they are the same function: f x = g x for all x f = g (provided x does not appear in f or g)

(You can think of this as cancelling the xs.) So the above law can be re-written as (map f . map g) = map (f . g)

More laws for map map f . map g = map (f . g) map id = id head . map f = f . head tail . map f = map f . tail length . map f = length The function lter We often want to select all those elements of a list that satisfy some property. The standard function filter does this. > filter :: (a -> Bool) -> [a] -> [a] > filter p [] = [] > filter p (x:xs) > | p x = x : filter p xs > | otherwise = filter p xs For example: filter even [1,2,3,4] = [2,4] Some laws: filter p . filter q = filter r filter p . map f = map f . filter (p . f) Aside: notation Sometimes its useful to dene an auxiliary function without giving it a name. The notation \ x -> e is equivalent to f where f x = e i.e., the function of x whose value is e. For example: (\ x -> x * x) 3 The rst law for filter can be rewritten as filter p . filter q = filter (\ x -> q x && p x) where r x = q x && p x

The function concat The function concat takes a list of lists and concatenates them all into a single list: > concat :: [[a]] -> [a] > concat [] = [] > concat (xs:xss) = xs ++ concat xss For example concat [[1,2,3], [], [4], [5,6]] Some laws: concat (xss ++ yss) = concat xss ++ concat yss map f . concat = concat . map (map f) filter p . concat = concat . map (filter p) The function zip The standard function zip takes a pair of lists and returns a list of pairs of corresponding elements: = [1,2,3,4,5,6]

> > > >

zip :: zip [] ys = zip xs [] = zip (x:xs) (y:ys) =

[a] -> [b] -> [(a,b)] [] [] (x,y) : zip xs ys

For example, zip [1,2,3] [a,b,c] = [(1,a),(2,b),(3,c)] Question 1.1. What happens if one list is shorter than the other? The function zipWith Sometimes it is useful to apply a function to each pair of elements: > > > > zipWith zipWith zipWith zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] f [] ys = [] f xs [] = [] f (x:xs) (y:ys) = f x y : zipWith f xs ys

We have the following laws: zip = zipWith (,) zipWith f = map (uncurry f) . zip where uncurry f (x,y) = f x y

Example: scalar product Example 1.2. The scalar product of two lists is the sum of the products of their entries: > scalarProduct :: Num a => [a] -> [a] -> a > scalarProduct xs ys = sum (zipWith (*) xs ys)

Example: testing whether a list is increasing Example 1.3. To test whether a list is increasing check each entry is smaller than the succeeding entry: > increasing :: Ord a => [a] -> Bool > increasing xs = and (zipWith (<) xs (tail xs)) > and :: [Bool] -> Bool > and [] = True > and (b:bs) = b && and bs

List enumerations The special syntax [m..n] represents the list of integers from m up to n. It abbreviates enumFromTo m n where > enumFromTo :: (Integral a) => a -> a -> [a] > enumFromTo m n = > if m <= n then m:enumFromTo (m+1) n else []

1.2

List comprehension

List comprehensions A list comprehension provides a way of dening a list, in a similar way to the use of set comprehensions for sets. For example: [p*p | p <- [1..5]] = [1, 4, 9, 16, 25] [p*p | p <- [1..5], isprime p] = [4, 9, 25] [(i,j) | i <- [1..3], j <- [1..2]] = [(1,1), (1,2), (2,1), (2,2), (3,1), (3,2)] [(i,j) | i <- [1..3], j <- [i..2], i <= j] = [(1,1), (1,2), (2,2)]

Rules of comprehensions The rules of list comprehensions are: LC1 [e | x <- xs] = map (\x -> e) xs LC2 [e | p] = if p then [e] else [] LC3 [e | x <- xs, Q] = concat (map f xs) LC4 [e | p, Q] = if p then [e | Q] else [] Other useful rules: LC5 [f x | x <- xs] = map f xs LC6 [x | x <- xs, p x] = filter p xs LC7 [e | Q, P] = concat [[e | P] | Q] 4 where f x = [e | Q]

A programming example

Example: printing a multiplication table Example 2.1. Write a function timestable that will print an m by n multiplication table, when given the arguments m and n. For example: ? timestable 6 7 | 1 2 3 4 5 6 7 -----------------------1 | 1 2 3 4 5 6 7 2 | 2 4 6 8 10 12 14 3 | 3 6 9 12 15 18 21 4 | 4 8 12 16 20 24 28 5 | 5 10 15 20 25 30 35 6 | 6 12 18 24 30 36 42 Calculating the table entries Its a good idea to separate the calculation of the entries in the table from the printing of it. > timestable :: Int -> Int -> String > timestable m n = > let table = [ [i*j | j <- [1..n]] | i <- [1..m] ] > ... > in ... table :: [[Int]] holds the entries for the table; each element of table holds the entries for one row. Calculating column widths We need to calculate the widths in which to display each column. Each column needs to be just wide enough to hold the widest entry, which will be in the bottom row. The left hand column needs to be the width of the string representing m, plus one: > w0 = ((+1) . length . show) m > -- width of left hand column Every other column needs to be the width of the string representing the entry in the corresponding column of the last row, plus one: > ws = map ((+1) . length . show) (last table) > -- widths of other columns

Aside : sectioning If is an inx operator, then ( m) n = n m = (n ) m = () n m This is called sectioning. Example 2.2. (+1) and (1+) are both functions that add one to their arguments. 5

Printing an entry in the left hand column Its useful to dene a function that formats an entry to appear in the left hand column: > showFirstCol :: Int -> String -> String > showFirstCol w0 st = rjustify (w0,st) ++ " |"

Helper functions The function rjustify formats the string st to appear in a column of width w, padding with spaces on the left: > rjustify :: (Int,String) -> String > rjustify (w,st) = spaces (w-length st) ++ st spaces n produces a string of n spaces: > spaces :: Int -> String > spaces n = replicate n replicate n x produces a list of n copies of x: > replicate :: Int -> a -> [a] > replicate 0 x = [] > replicate (n+1) x = x : replicate n x

Producing a row of the table The function showRow takes a list ws of column widths, and a list row of entries, and formats each entry in a eld of the appropriate width, and concatenates them all together. > showRow :: [Int] -> [Int] -> String > showRow ws row = > concat [rjustify (w, show k) | > (w,k) <- zip ws row] ++ "\n" We could alternatively have dened showRow as: > showRow ws row = > (concat . map rjustify) > (zip ws (map show row))++"\n"

Printing the rst two rows We can print the rst row by printing an empty string in the left hand column, and printing [1..n] in the rest of the row. We can print the second row by producing an appropriate number of - symbols. > timestable :: Int -> Int -> String > timestable m n = > let table = [ [i*j | j <- [1..n]] | i <- [1..m] ] > w0 = ((+1) . length . show) m > -- width of left hand column > ws = map ((+1) . length . show) (last table) > -- widths of columns > in showFirstCol w0 "" ++ showRow ws [1..n] ++ > ...replicate (w0+sum ws+3) - ++ "\n" ++ ...

Producing the rest of the rows A single row with entries in row can be produced by: > showFirstCol w0 (show (head row)) ++ showRow ws row Note that head row is the entry to appear in the left hand column. We need to do this to every row in table, and concatenate the results: > concat > [showFirstCol w0 (show (head row)) ++ > showRow ws row |row <- table]

Outputting strings Just converting the result to a string is not enough; we need to cause it to be displayed properly. The function putStr :: String -> IO () takes a string, and performs some I/O (input/output). The main function > timestable :: Int -> Int -> IO() > timestable m n = > let table = [ [i*j | j <- [1..n]] | i <- [1..m] ] > w0 = ((+1) . length . show) m > -- width of left hand column > ws = map ((+1) . length . show) (last table) > -- widths of columns > in putStr ( > showFirstCol w0 "" ++ showRow ws [1..n] ++ > replicate (w0+sum ws+3) - ++ "\n" ++ > concat > [showFirstCol w0 (show (head row)) ++ > showRow ws row | row <- table] > )

More list functions

The functions take and drop take n xs returns the rst n elements of xs; drop n xs returns all but the rst n elements of xs. > > > > > > > > take take 0 xs take n [] take (n+1) (x:xs) drop drop 0 xs drop n [] drop (n+1) (x:xs) :: Int -> [a] -> [a] = [] = [] = x : take n xs :: Int -> [a] -> [a] = xs = [] = drop n xs

Note that take n xs ++ drop n xs = xs

The functions takeWhile and dropWhile The function takeWhile p xs returns the longest initial segment of xs all of whose elements satisfy p. The function dropWhile p xs returns the remainder of xs. > takeWhile > takeWhile p [] > takeWhile p (x:xs) > | p x > | otherwise > dropWhile > dropWhile p [] > dropWhile p (x:xs) > | p x > | otherwise :: (a -> Bool) -> [a] -> [a] = [] = x : takeWhile p xs = [] :: (a -> Bool) -> [a] -> [a] = [] = dropWhile p xs = x:xs

Examples Example 3.1. takeWhile even [2,4,6,7,4,5] = [2,4,6] dropWhile even [2,4,6,7,4,5] = [7,4,5] Note that if p never returns when applied to an element of xs, then xs = takeWhile p xs ++ dropWhile p xs

Reversing a list The standard function reverse reverses a list: > reverse :: [a] -> [a] > reverse [] = [] > reverse (x:xs) = reverse xs ++ [x] This denition of reverse is rather inecient; well see a better denition later.

A common pattern

A common pattern of denition sum [] = 0 sum (x:xs) = x + sum xs product [] = 1 product (x:xs) = x * product xs concat [] = [] 8

concat (xs:xss) =

xs ++ concat xss

reverse [] = [] reverse (x:xs) = reverse xs ++ [x] filter p [] = [] filter p (x:xs) = if p x then x:filter p xs else filter p xs

Common patterns of proof Similarly, the proofs of the following laws all have a common pattern: map f (xs ++ ys) filter p (xs ++ ys) concat (xss ++ yss) sum (xs ++ ys) product (xs ++ ys) = = = = = map f xs ++ map f ys filter p xs ++ filter p ys concat xss ++ concat yss sum xs + sum ys product xs * product ys

The function foldr Many functions dened so far take the form: > foo [] = e > foo (x:xs) = x foo xs In other words, if xs = [x0, x1, ..., xn] then foo xs = x0 (x1 (... (xn e))) The function foldr encapsulates this pattern: > foldr :: (a -> b -> b) -> b -> [a] -> b > foldr f e [] = e > foldr f e (x:xs) = f x (foldr f e xs)

Converting recursive denitions into folds Suppose we have a function foo satisfying the equations: foo [] = e foo (x:xs) = f x (foo xs) Then foo = foldr f e (at least when restricted to nite lists). This allows us to convert recursive equations into foldrs.

Examples map f = foldr g [] where g x xs = f x : xs filter p = foldr g [] concat = foldr (++) [] reverse = foldr snoc [] where snoc x xs = xs++[x] sum = ... The functions and and or The function and tests whether all the elements of its argument are True; the function or tests whether any of its elements is True. The functions can be dened recursively by: > and :: [Bool] -> Bool > and [] = True > and (b:bs) = b && and bs > or :: [Bool] -> Bool > or [] = False > or (b:bs) = b || or bs Question 4.2. Can we dene these functions using a foldr? Maximum Question 4.3. Dene a function that calculates the maximum element in a list using foldr. The function foldr1 foldr1 is like foldr, except it operates over non-empty lists. foldr1 () [x0 , x1 , ..., xn1 , xn ] = x0 (x1 (... (xn1 xn )...)) It can be dened by: > foldr1 :: (a -> a -> a) -> [a] -> a > foldr1 f [x] = x > foldr1 f (x:xs) = f x (foldr1 f xs) The function foldl Not every function over lists can be easily dened using foldr; for example: decimal [x0,x1,x2] = x0 * 102 + x1 * 101 + x2 * 100 decimal [x0,x1,x2] = ((0 x0) x1) x2 where n x = 10*n + x The function foldl captures this pattern: > foldl :: (b -> a -> b) -> b -> [a] -> b > foldl f e [] = e > foldl f e (x:xs) = foldl f (f e x) xs Note that the argument e changes from one call to another: it is an accumulating parameter. 10 where g x xs = if p x then x : xs else xs

foldl versus foldr Many functions denable using foldr can also be dened using foldl Example 4.4. > sum = foldl (+) 0 > product = foldl (*) 1 (In fact, these denitions using foldl might be slightly more ecient.) The function foldl1 Theres a version of foldl1 that operates over non-empty lists: > foldl1 :: (a -> a -> a) -> [a] -> a > foldl1 f (x:xs) = foldl f x xs Question 4.5. What does this return for a list [x] of length 1? The function scanl The function scanl f e applies foldl f e to each of the initial segments of its argument: > scanl :: (b -> a -> b) -> b -> [a] -> [b] > scanl f e = map (foldl f e) . inits > inits :: [a] -> [[a]] > inits [] = [[]] > inits (x:xs) = [] : map (x:) (inits xs) For example: > inits [1,2,3,4] = [[],[1],[1,2],[1,2,3],[1,2,3,4]] > scanl (+) 0 [1,2,3,4] = [0,1,3,6,10]

Eciency We have dened scanl like this: > scanl :: (b -> a -> b) -> b -> [a] -> [b] > scanl f e = map (foldl f e) . inits Question 4.6. Can you see any way in which scanl is inecient? A more ecient implementation = = = = scanl f e [] { denition of scanl } map (foldl f e) (inits []) { denition of inits } map (foldl f e) [[]] { denition of map } [foldl f e []] { denition of foldl } [e]

11

A more ecient implementation scanl f e (x:xs) { denition of scanl } map (foldl f e) (inits (x:xs)) { denition of inits } map (foldl f e) ([] : map (x:) (inits xs)) { denition of map } foldl f e [] : map (foldl f e) (map (x:) (inits xs)) { denition of foldl; map g . map h = map (g . h) } e : map (foldl f e . (x:)) (inits xs) { claim: foldl f e . (x:) = foldl f (f e x) } e : map (foldl f (f e x)) (inits xs) { denition of scanl } e : scanl f (f e x) xs

= = = = = =

Proof of claim (foldl f e . (x:)) xs { denition of . } foldl f e (x:xs) { denition of foldl } foldl f (f e x) xs

= =

A more ecient implementation We have derived: > scanl f e [] = [e] > scanl f e (x:xs) = e : scanl f (f e x) xs This implementation evaluates f only once for each element in the original list. Question 4.7. Can we do the same simplication for scanr f e = map (foldr f e) . tails where the function tails :: [a] -> [[a]] returns all the tail segments of a list. Summary common functions on lists: map, filter, concat, zip, zipWith, sum, reverse, take, drop, takeWhile, dropWhile; algebraic laws can be used to transform a program; list comprehensions; try to break large programming problems into smaller chunks; try to spot patterns of problems, and so reuse code; dening functions using folds.

12

You might also like