(* Some example SML code *) (* Comments begin with "(*" and end with "*)". Obviously, they nest... *) (* Bind a value to a variable *) val pi : real = 3.14159 (* Define a function *) val square : real -> real = fn (x:real) => x*x val area : real -> real = fn (r:real) => pi * square(r) (* Define a recursive function *) val rec fact : int -> int = fn (n:int) => if (n <= 0) then 1 else n * fact(n-1) (* It's more convenient to define functions with "fun" *) fun square(x:real):real = x*x fun area(r:real):real = pi * square(r) fun fact(n:int):int = if n < 0 then 1 else n * fact(n-1) (* You [usually] can leave off the types...ML will figure them out. *) val pi = 3.14159 fun fact n = if n < 0 then 1 else n * fact(n-1) (* But it's a good idea to put them on for two reasons: * (1) better documentation for the reader * (2) you get better error messages from the compiler * (3) certain overloaded operations (e.g., +,*,-, etc.) * cause type-inference headaches. * Moral: when in doubt, put the types on. *) (* Local declarations *) fun area(r:real):real = let fun square(x:real):real = x*x in pi * square(r) end (* This version of factorial is tail-recursive *) fun fact(n:int):int = let fun loop(c:int, a:int):int = if (c <= 0) then a else loop(c-1, c*a) in loop(n,1) end (*****************************************************) (* Define a new inductive type nat that is either Zero or the * Successor of a previous nat. *) datatype nat = Zero | Succ of nat (* You can think of this as defining a new type "nat" as well * as two ways to build a nat: * * Zero : nat * Succ : nat -> nat * * So we call Zero and Succ the constructors of the type "nat". * * In addition, we get the guarantee that there is *no other way* * to construct a nat. That's very important... *) (* Some example nats *) val three = Succ(Succ(Succ(Zero))) val five = Succ(Succ(three)) (* Define a new datatype corresponding to booleans: *) datatype Bool = True | False fun not(x:Bool):Bool = case x of True => False | False => True fun isZero(n:nat):Bool = case n of Zero => True | Succ(m:nat) => False (* Map a nat two an ML integer *) fun nat2int(n:nat):int = case n of Zero => 0 | Succ(m:nat) => 1 + nat2int(m) (* Add two naturals *) fun add(n:nat, m:nat):nat = case n of Zero => m | Succ(n':nat) => add(n', Succ(m)) (* add looks like it takes two arguments, but it really only takes 1 argument that is a pair of nats --- nat * nat. When we desugar it, it becomes something like this: *) val rec add : nat * nat -> nat = fn (p:nat * nat) => (case p of (n:nat, m:nat) => (case n of Zero => m | Succ(n':nat) => add(n', Succ(m)))) val pair : nat * nat = (three, five) val n : nat = add pair (* Moral -- every function in ML takes 1 argument and returns 1 result. If you want to use more than one argument or one result, then you can use a tuple. *) (* An intlist is either Nil or a Cons(i,x) where i is an int and x is an intlist. Note the similarity with nat. *) datatype intlist = Nil | Cons of int * intlist val list3 : intlist = Cons(3,Cons(2,Cons(1,Nil))) val list5 : intlist = Cons(5,Cons(4,list3)) fun merge(x:intlist, y:intlist):intlist = case (x,y) of (Nil,_) => y | (_,Nil) => x | (Cons(i,x'),Cons(j,y')) => if (i < j) then Cons(i,merge(x',y)) else Cons(j,merge(x,y')) fun split(x:intlist, y:intlist, z:intlist):(intlist * intlist) = case x of Nil => (y,z) | Cons(i,x') => split(x', z, Cons(i,y)) fun mergesort(x:intlist):intlist = case x of Nil => Nil | Cons(_,Nil) => x | _ => let val (a,b) = split(x,Nil,Nil) in merge(mergesort a, mergesort b) end (* before running this, it's useful to set the interactive loop's printDepth to something bigger than 5: Control.Print.printDepth := 42; *) (*********************************************************** * SML has built in generic or polymorphic lists that look like this * * datatype 'a list = nil | :: of 'a * ('a list) * * where "::" is an infix identifier and the "'a" represents a type variable. *) val list3 : int list = 3::2::1::nil val list5 : int list = 5::4::list3 val list3 : int list = [3,2,1] (* brackets can be used to abbreviate lists *) val list5 : int list = [5,4,3,2,1] val listb : bool list = true::false::true::true::nil val listb : bool list = [true,false,true,true] val strings : string list = ["Greg", "Amy", "John", "Tanya"] val confusing : (string list) list = [["Greg", "Prof", "Old"], ["Amy", "Student", "9"], ["John", "Student", "7"], ["Tanya", "Teacher", "Old"]] datatype IntOrString = Int of int | String of string val mixed = [Int 42, String "Greg", Int 12, String "Amy", String "John"] (* generic sorting function *) (* note the builtin datatype: * datatype order = LESS | EQUAL | GREATER * is pre-defined. *) fun mergesort(cmp:'a*'a->order) (x:'a list) : 'a list = let fun merge(x:'a list, y:'a list) = case (x,y) of (nil,_) => y | (_,nil) => x | (i::x',j::y') => (case cmp(i,j) of LESS => i::merge(x',y) | _ => j::merge(x,y')) fun split(x:'a list, y:'a list, z:'a list) = case x of nil => (y,z) | i::x' => split(x', z, i::y) fun both f (x,y) = (f x, f y) in case x of nil => nil | _::nil => x | _ => merge(both (mergesort cmp) (split(x,nil,nil))) end (* specialize merge sort to integers using library Int.compare *) val intmergesort = mergesort Int.compare (* specialize to strings using String.compare *) val stringmergesort = mergesort String.compare (* specialize to lists -- parameterize by element ordering *) fun list_compare (elt_compare : 'a*'a->order) : ('a list * 'a list) -> order = let fun cmp([],[]) = EQUAL | cmp([],_) = LESS | cmp(_,[]) = GREATER | cmp(h1::t1,h2::t2) = (case elt_compare(h1,h2) of EQUAL => cmp(t1,t2) | r => r) in cmp end (* now we can sort a list of lists of strings! *) val stringlistmergesort = mergesort (list_compare String.compare) (* specialize to a mixed list using our own ordering *) fun mixed_compare(x:IntOrString, y:IntOrString):order = case (x,y) of (Int i, Int j) => Int.compare(i,j) | (String s, String t) => String.compare(s,t) | (Int i, String s) => LESS | (String s, Int i) => GREATER val mixedmergesort = mergesort mixed_compare (********************************************************************) (* A generic for-loop *) fun for (start:int) (cmp:int*int->bool) (stop:int) (inc:int->int) (body:int*'a -> 'a) : 'a -> 'a = let fun loop(i:int,accum:'a):'a = if cmp(i,stop) then let val new_accum = body(i,accum) val new_i = inc i in loop(new_i,new_accum) end else accum in fn (a:'a) => loop(start,a) end (* some useful abbreviations *) fun ++(i:int) = i+1 fun --(i:int) = i-1 val gt = op > val lt = op < val cons = op :: (* generate the list of numbers from 1 to 10 *) val one_to_ten = for 10 gt 0 -- cons [] (* print the numbers from 1 to 10 *) val _ = for 1 lt 11 ++ (fn (i,x:unit) => (print(Int.toString i); print "\n")) (); (* generate the odd numbers from 1 to 9 *) val odd_one_to_ten = for 9 gt 0 (fn i => i - 2) cons [] (* generate numbers from 1 to n *) fun one_to_n(n:int) = for n gt 0 -- cons [] (********************************************************************) (* sum up the numbers in a list *) (********************************************************************) fun sum(x:int list):int = let fun loop(x:int list,accum:int) = case x of [] => accum | i::rest => loop(rest,i+accum) in loop(x,0) end (* take the product of a list. hmmm...this looks familiar *) fun prod(x:int list):int = let fun loop(x:int list,accum:int) = case x of [] => accum | i::rest => loop(rest,i*accum) in loop(x,1) end (* factor out the common bits *) fun foldl (combine : int*int->int) (zero:int) (x:int list):int = let fun loop(x:int list,accum:int) = case x of [] => accum | i::rest => loop(rest,combine(i,accum)) in loop(x,zero) end val sum = foldl (op +) 0 val prod = foldl (op * ) 1 (* or even better...we can make it type-generic! *) fun foldl (combine : 'a*'b->'b) (zero:'b) (x:'a list):'b = let fun loop(x:'a list,accum:'b) = case x of [] => accum | i::rest => loop(rest,combine(i,accum)) in loop(x,zero) end (* now we can operate over lists of integers or lists of strings! *) val sum = foldl (op +) 0 val prod = foldl (op * ) 1 val concat = foldl (fn (x,y) => y ^ x) "" val s = concat ["Greg","Amy","John"] (* hard to write a smaller list reversal function, no? *) fun rev x = foldl (op ::) nil x (* reverses x onto y *) fun revappend x y = foldl (op ::) y x (* so to append two lists x and y, we can just reverse the first * and then revappend it onto y. *) fun append x y = revappend (rev x) y (* this function applies f to every element of the list and builds * up a new list of the results in reverse order. *) fun map_rev (f:'a->'b) : 'a list -> 'b list = foldl (fn (hd,tl) => (f hd)::tl) nil (* now we can compose map_rev with rev to get a function map * which applies f to evey element of x building up the * result in the proper order. *) fun map f x = (rev o (map_rev f)) x (******************************************************************) (* r is a mutable reference, with contents 0 *) val r : int ref = ref 0 (* to read out the contents, use ! *) val rvalue : int = !r (* to update the contents, use := *) val _ : unit = r := 42 (* a counter object *) type counter = { read : unit -> int, inc : unit -> unit } (* a class where the counter is an integer *) fun int_counter() = let val c = ref 0 fun r() = !c fun i() = c := r() + 1 in {read = r, inc = i} end (* a class where the counter is a real *) fun real_counter() = let val c = ref 0.0 fun r() = Real.round(!c) fun i() = c := (!c) + 1.0 in {read = r, inc = i} end (* a class where the counter is a bool *) fun bool_counter() = let val c = ref true fun r() = if (!c) then 1 else 0 fun i() = c := (if (!c) then false else true) in {read = r, inc = i} end fun gen_counter(i:int):counter = case i mod 3 of 0 => int_counter() | 1 => real_counter() | _ => bool_counter() (* build a list of 10 counters *) val counters = for 0 (op <) 10 ++ (fn (i,cs) => (gen_counter i)::cs) nil (* increment all of the counters *) val _ = map (fn {inc,...} => inc()) counters