[Notes by Tibor Janosi] 1. List operations: ::, @, null, length, hd, tl, rev, map, foldl. Give examples! You can create lists of any underlying type, including another list type. The type of [] can not be infered by just looking at its representation. If, however, [] appears as part of a more complex expression, its type might be determined. 1::[] -> Here [] is of type int list. Why? Lists must be type-homogeneous, so if we add an int to a list, the underlying type of [] must be int list. Yet another example of type inference! [[[3]]] is of type int list list list [["ab", "cd"], [], ["def"]] is of type string list list 1::[] --> [1] 1::2::3::[4, 5] --> [1, 2, 3, 4, 5] [] @ [] = [] [1, 2] @ [3, 4, 5] = [1, 2, 3, 4, 5] null [] = true null [2, 3] = false hd [1, 2, 3] = 1 hd [1] = 1 hd [] --> error tl [1, 2, 3] = [2, 3] tl [1] = [] tl [] --> error Note that @ is ineficient - in particular it is "harder" to add an element at the end of the list then to prepend it. map (fn x: int => x * x) [1, 2, 3, 4] map (fn x: int => x > 0) [~1, 0, 1, 2] foldl (fn (x:int, s:int) => x + s) 0 [1, 2, 3, 4] foldl (fn (x:int, p:int) => x * s) 1 [1, 2, 3, 4] Foldl and foldr allow for very sophisticated list processing. We will see much more complicated examples later. 2. More advanced examples: pattern matching, list processing, inner ("helper") functions, recursion Here is a more interesting case of pattern matching (see below). We want to determine whether the two list arguments have length at least 2, and is they do, whether their second elements are equal or not. fun secondEqual (x: int list * int list): bool = case x of ([], _) => false | ([_], _) => false | (_, []) => false | (_, [_]) => false | (_::v1::_, _::v2::_) => v1 = v2; The version above shows several interesting cases of pattern matching. A much better implementation is the following: fun secondEqual (x: int list * int list): bool = case x of (_::v1::_, _::v2::_) => v1 = v2 | _ => false We could have done some pattern matching in the function header as well: fun secondEqual (x: int list, y: int list): bool = case (x, y) of ... Finally, here is a curried version of the same functon: fun secondEqual (x: int list) (y: int list): bool = case (x, y) of ... And here are some other examples you might find useful: fun max (x: int list): int = let fun helper(x: int list, crtmax: int): int = case x of [] => crtmax | h::t => if h > crtmax then helper(t, h) else helper(t, crtmax) in case x of [] => raise Fail "empty list" | h::t => helper(t, h) end; fun sumAll (x: int list): int = case x of [] => 0 | h::t => h + sumAll t; fun sumAll (x: int list): int = let fun helper(x: int list, sum: int): int = case x of [] => sum | h::t => helper(t, h + sum) in helper(x, 0) end; fun duplicate (x: int list): int list = case x of [] => [] | h::t => h::h::(duplicate t); fun duplicate (x: int list): int list = let fun helper(x: int list, acc: int list): int list = case x of [] => acc | h::t => helper(t, h::h::acc) in rev (helper(x, [])) end; ---------------------------------------------------------------------- Homework notes: You can load a file using the following command: use "file name" (quotes are significant). You should not worry too much about what a signature or a structure is. You don't have to touch the signature file, but you need to load it. Edit the structure file, then load it. If there are no errors reported, you can then test your functions. You can test your functions by typing them directly (i.e. without loading the structure file). Some find this more convenient.