(* Meeting 10: SML: Datatypes, Type Inference, and Polymorphism *) exception Unimplemented; (*******************************************************************) (* REVIEW *) (* Lists *) []; [3]; nil; 3 :: []; nil; (op ::); (* val len : 'a list -> int Returns the length of a list. *) fun len l = if (l = nil) then 0 else 1 + len (tl l) fun len nil = 0 | len (_ :: t) = 1 + len t (* EXERCISE: Some more tail recursion practice. Write len tail recursively. *) fun len l = let fun len' (nil, acc) = acc | len' (_ :: t, acc) = len' (t, acc + 1) in len' (l, 0) end (* EXERCISE: Write a function that appends two lists. *) fun append (nil, l) = l | append (h :: t, l) = h :: append (t, l) (* Reverse *) fun rev nil = nil | rev (h :: t) = append (rev t, [h]) fun rev nil = nil | rev (h :: t) = rev t @ [h] (* using built-in append function *) (* EXERCISE: Write a tail recursive list reverse function. *) fun rev l = let fun rev' (nil, acc) = acc | rev' (h :: t, acc) = rev' (t, h :: acc) in rev' (l, nil) end (*******************************************************************) (* Datatypes *) (* Enumerations *) datatype mybool = True | False fun mynot True = False (* pattern match *) | mynot False = True (* Parameterized by types *) datatype 'a option = NONE | SOME of 'a (* Is a built-in type. *) (* What are some possible uses? *) fun reciprocal 0 = NONE | reciprocal n = SOME (1 div n) (* Recursive datatypes *) datatype myintlist = Nil | Cons of int * myintlist; Nil; Cons; Cons (3, Cons (4, Nil)); datatype 'a mylist = Nil | Cons of 'a * 'a mylist; Nil; Cons; type mynewintlist = int mylist; Cons (3, Cons (4, Nil)) : mynewintlist; (* EXERCISE: Define a binary tree datatype that is either empty or node with some data of type 'a and two subtrees. Then, define a function height : 'a tree -> int that returns the height of the tree. Int.max : int * int -> int returns the max of two integer values. *) datatype 'a tree = Empty | Node of 'a tree * 'a * 'a tree fun height Empty = 0 | height (Node (l, _, r)) = 1 + Int.max (height l, height r) (* In dynamically-typed languages, we have the ability create hetereogenous data structures, like [3, "abc", 4, "def"] In SML, we can create such structures as well. How? *) datatype int_or_string = Int of int | String of string val x = [Int 3, String "abc", Int 4, String "def"] (* Abstract Syntax *) (* One of the main benefits of using a language like SML is that it is easy to work with abstract syntax. *) (* Our favorite arithmetic expression language translates directly into a datatype. e ::= n | e + e | e * e *) datatype expr = Num of int | Plus of expr * expr | Times of expr * expr (* EXERCISE: Write an evaluation function for this language. eval : expr -> int *) fun eval (Num n) = n | eval (Plus (e1, e2)) = (eval e1) + (eval e2) | eval (Times (e1, e2)) = (eval e1) * (eval e2) (*******************************************************************) (* Parametric Polymorphism *) (* We already saw datatypes that are parametric in another type (e.g., the built-in lists). Same thing applies to functions. *) (* The identity function should work on any type. *) fun id (x: int) : int = x fun id (x: real) : real = x fun id (x: string) : string = x fun id (x: 'a) : 'a = x (* 'a is called a type variable and 'a -> 'a is called a type scheme *) fun id x = x val id = (fn x => x) (* Type inference gives us the most general (i.e., least restrictive) type. The ability to do this called principal typing. *) (* What are principal types of the following functions? *) fun f (x, y, z) = y fun g (h :: t) = h (* A pecularity *) val weird = id id (* Value restriction: val declarations may be inferred to have polymorphic type only if the right-hand side is a value. *) (* Necessary for soundness of the type system in the presence of side-effects (e.g., references). *) (*******************************************************************)