Com S 541 Lecture -*- Outline -*- > module ListLecture where > import Prelude hiding (fst, snd, head, tail, filter, zip) * lists (Thompson 4, Davie 2.8, 3.11) ------------------------------------------ LISTS IN HASKELL [a] -- homogeneous lists of a Values: + abstract values: sequences of a's + printed: [], [0], [1,2,3,...] Operations: + constructors: [], : + functions: head, tail, last, init, null, ++, length, !!, map, take, drop, reverse, all, any, ... + syntax: [1,2,3] = 1:2:3:[] [1 ..] = enumFrom 1 [1,3 ..] = enumFromThen 1 3 [1 .. 8] = enumFromTo 1 10 [1,3 ..8] = enumFromThenTo 1 3 8 [e | e <- [1 ..], even e] = do e <- [1 ..] guard (even e) return e ------------------------------------------ e.g. [Integer] is a list of integers [String] is a list of strings, etc. Note: String = [Char] lists are homogeneous, and not necessarily finite. ** Haskell list features as a DSL ** sugars 1:[] = [1] 1:2:[] = (1:(2:[])) = 1:[2] = [1,2] [1,2,1,3] can have repeats [1,1,2,3] order matters *** dot dot (..) notation [n .. m] = [n, n+1, n+2, ..., m] if n <= m [] otherwise e.g., [3 .. 7] = [3, 4, 5, 6, 7] [3 .. 3] = [3] [3 .. 2] = [] [n, p .. m] = [n, n + (p-n), ..., m] e.g., [3, 5 .. 11] = [3, 5, 7, 9, 11] [3, 5 .. 12] = [3, 5, 7, 9, 11] [5, 3 .. 0] = [5, 3, 1] ['a', 'b' .. 'e'] = "abcde" [2, 2 .. 2] = [2,2,2,2,2, ...] The general rule (Report, section 3.10) is that [e1,e2 .. e3] gives a list of values starting at e1, with increment e2-e1 of values not greater than e3 (assuming e2-e1 is positive) *** for infinite lists [n ..] = [n, n+1, n+2, ...] e.g., [1 .. ] = [1, 2, 3, 4, ...] [7 .. ] = [7, 8, ...] [n, p ..] = [n, n + (p-n), ...] e.g., [2, 4 ..] = [2, 4, 6, 8, ...] ** list comprehensions Analogy to set comprehensions That 90% of loops can be handled by mapping and filtering combinations comes from The Progammer's Apprentice work of Charles Rich and Richard Waters. > ex = [2, 4, 7, 3, 2] > integers = [ 1 .. ] *** mapping [ 2 * n | n <- ex] write product_by, map *** filtering [ n | n <- ex, odd n ] = [7, 3] write list of odd integers greater than 2, all integers divisible by 3 squares of all integers divisible by 7 and less than 100 write filter using a comprehension *** using patterns write addPairs, addOrderedPairs *** nested maps [(a,b) | a <- ex, b <- [1,2]] = [(2,1), (2,2), (4,1), (4,2), (7,1), (7,2), (3,1), (3,2), (2,1), (2,2)] ** built-in functions, standard Prelude (go quickly or skip) : and [] of course *** zip and unzip allows you to do simultaneous looping, instead of nested use zip and a comprehension to write addLists :: [Integer] -> [Integer] -> [Integer] another problem, write: findIndices :: (a->Bool) -> [a] -> [Int] such that findIndices even [4..9] = [0,2,4] findIndices odd [] = [] findIndices odd [3,5,2,0] = [0,1] *** ++, !!, concat, length, head, last, tail, init concat :: [[a]] -> [a] > type Movie = [Picture] > type Picture = [Line] > type Line = [Pixel] > type Pixel = Int write firstFrame :: Movie -> Picture splice :: Movie -> Movie -> Movie -> Movie runningTime :: Movie -> (Int, Int) *** replicate, take, drop fastForward :: (Movie, Movie) -> (Movie, Movie) freezeFrame :: Int -> (Movie, Movie) -> (Movie, Movie) *** splitAt, reverse, and, or, any, all, sum, product give examples of these *** foldr and foldl (Thompson 9.3) e.g., foldr (-) 0 [5,4 ..1] = 5 - (4 - (3 - (2 - (1 - 0)))) = 3 foldl (-) 0 [5,4,3,2,1] = ((((0 - 5) - 4) - 3) - 2) - 1 = -15 look in the Prelude at how these are used to program various procedures ** explicit recursions. Can't always use a comprehension (e.g., when have to reorder to sort) discuss the "one step and rest of the journey" idea that the idea of the two cases: base cases and inductive case also working from examples is a feature of the discussion below *** practice ------------------------------------------ FOR YOU TO DO Write a function (++) :: [a] -> [a] -> [a] so that: [1,2,3] ++ [4,5] = [1,2,3,4,5] [] ++ [7,8] = [7,8] Write a function all :: (a -> Bool) -> [a] -> Bool so that: all even [] = True all even [1,2,3] = False all even [2,4 .. 20] = True ------------------------------------------ have them develop this do induction on the first argument. Why? because can put stuff on front easily with : Q: what is the base case? Q: take the above example for the inductive case. what do we want? what are we given? how do you get that? Q: so what are the equations? equations: [] ++ y = y (x:lst) ++ y = x:(lst ++ y) (No good way to do append tail-recursively) note: can't compare lists of different types compare def of all to that in the Prelude (and note any) *** more practice concat, maximum, lookup, zip, equal *** higher-order examples ------------------------------------------ FLAT RECURSION OVER LISTS Example: map :: (a -> b) -> [a] -> [b] such that map odd [] = [] map even [1..3] = [False, True, False] map negate [1 .. 5] = [-1,-2,-3,-4,-5] map (+ 1) [1 .. 4] = [2,3,4,5] ------------------------------------------ lists are generated inductively by [] and : as if we could write data [a] = [] | a : [a] this definition is the idea, but you can't actually write that in Haskell Q: So what will the cases be? develop this: base case: map f [] = [] inductive case: want map even 1:(2:3:[]) = False:True:False:[] given map even 2:3:[] = True:False:[] Q: how do we get what False:(True:False:[]) from (True:False:[]) and 1? generalizing from this example, map f (x:xs) = f x : (map f xs) this pattern is called flat recursion over lists (or recursion over flat lists) other examples: (possibly concatMap), filter, iterate ** tail recursion: no pending computation on recursive calls (Davie 3.9) *** example ------------------------------------------ FULL vs. TAIL RECURSION Fully recursive > len [] = 0 > len (x:xs) = 1 + (len xs) len [5,7,9] = 1 + (len [7,9]) = 1 + (1 + (len [9])) = 1 + (1 + (1 + (len []))) = 1 + (1 + (1 + (0))) = 1 + 1 + 1 = 1 + 2 = 3 ------------------------------------------ this is a picture of the run-time stack. can do this with only constant space by using an auxillary argument i.e, if want something like a variable, make it an argument to an auxilliary function return aux variable when would exit the loop assign it an inital value by passing it along assign all variables simultaneously by recursive call look: it's our friend the while loop! ------------------------------------------ TAIL RECURSIVE VERSION ------------------------------------------ transform fully recursive version ... add a counter, what would it start with... *show equational execution of (len [1,2,3]) len [1,2,3] = len_iter([1,2,3], 0) = len_iter([2,3], 1) = len_iter([3], 2) = len_iter([], 3) = 3 call this the *sequence of iterates* The key to designing a tail recursion is designing this sequence... > len2 lst = len_iter(lst, 0); > len_iter([],count) = count > len_iter(x:lst,count) = len_iter(lst,1+count); Tail-recursive function can be run using only constant amount of space. *** practice ------------------------------------------ FOR YOU TO DO Write > reverse :: [a] -> [a] > reverse [] = [] > reverse (x:xs) = (reverse xs) ++ [x] tail recursively. ------------------------------------------ to make a tail-recursive version, postulate the iterator reverse x = reverse_iter(x,y) what's the right value for y? will want to build up the answer in the extra variable so that reverse_iter([],y) = y so to figure value of y, use reverse [] = [] = reverse_iter([],y) = y so y = [] so now the definition is reverse x = reverse_iter(x,[]) reverse_iter([],y) = y What is the inductive case? That is, what should be the value of reverse_iter(x:xs,y)? Key is to design the sequence of iterates: reverse([1,2,3]) = reverse_iter([1,2,3], []) = reverse_iter([2,3], [1]) = reverse_iter([3], [2,1]) = reverse_iter([], [3,2,1]) = [3,2,1] Q: so what is reverse_iter(x:xs,y)? reverse_iter(xs,x:y) The importance of this is for efficiency to be able to return directly to caller, without having to pass a value back through pending computations *** when to use tail recursion ------------------------------------------ WHEN TO USE TAIL RECURSION ------------------------------------------ ... for efficiency when manipulating random-access collections (arrays) when you need an index (a for loop) if you want to return without going through recursions > index_of :: Eq a => a -> [a] -> Integer > index_of x ys = index_iter ys 0 > where index_iter [] i = -1 > index_iter (y:ys) i > | x == y = i > | otherwise = index_iter ys (i+1)