CS 334
|
tuples, records, lists
(17,"abc", true) : int * string * bool
{name = "bob",salary = 50000.99, rank=1}: {name: string, salary:real, rank:int}
Ex. of function on tuples:
- fun power (m,n) = if n = 0 then 1 else m * power (m,n-1); val power = fn : (int * int) -> int
On the other hand
- fun cpower m n = if n = 0 then 1 else m * cpower m (n-1); val cpower = fn : int -> (int -> int)
Note these are different functions!
Latter said to be in "Curried" form (after Haskell Curry).
Can define
- val twopower = cpower 2 val twopower = fn : int -> int - twopower 3; val it = 8 : int
[2,3,4,5,6] - all elts must be of same type.
Operations:length
@
- append - e.g. [1,2,3]@[4,5,6] =
[1,2,3,4,5,6]
::
- prefix (e.g. 1::x = [1,2,3,4,5,6]
)
map
- apply function to all elements of a list,
e.g. map sqr [1,2,4] = [1,4,16]
rev
- reverses list
[], nil
- empty list
Most common mistake of new ML programmers - confusing "::" and "@".
Many kinds of lists:
int list: [1,2,3]
string list: ["ab","cd","ef"]
nil
is part of any list type,
- nil; val it = [] : 'a list
where 'a stands for a type variable. Similarly write:
- map; val it = fn: ('a -> 'b) -> (('a list) -> ('b list))
Map is first example of a polymorphic function.
Lists are built up using ::, can also be decomposed the same way,
[1,2,3] = 1::[2,3] = 1::2::[3] = 1::2::3::nil
Can define functions by cases.
- fun product [] : int = 1 = | product (fst::rest) = fst * (product rest);
Note that "=" is automatically printed on continuation line. Don't include it in your program files!
Can also use integers in patterns:
- fun oneTo 0 = [] = | oneTo n = n::(oneTo (n-1)); - fun fact n = product (oneTo n);
Note oneTo 5 = [5,4,3,2,1]
Could have written
val fact = product o oneTo (* o is fcn. comp. *)
Here is how we could define a reverse fcn if it were not provided:
- fun reverse [] = [] = | reverse (h::t) = reverse(t)@[h]; (* pattern matching *)
Rarely use hd or tl - list operators giving head and tail of list.
Note that hd (a::x) = a, tl(a::x) = x, and ((hd x) :: (tl x)) = x
if x is a list with at least one element.
Can use pattern matching in relatively complex ways to bind variables:
- val (x,y) = (5 div 2, 5 mod 2); val x = 2 : int val y = 1 : int - val head::tail = [1,2,3]; val head = 1 : int val tail = [2,3] : int list - val {a = x, b = y} = {b = 3, a = "one"}; val x = "one" : string val y = 3 : int - val head::_ = [4,5,6]; (* note use of wildcard "_" *) val head = 4 : int
Thus
hd : ('a list) -> 'a tl : ('a list) -> ('a list)
Define
fun last [x] = x | last (fst::snd::rest) = last (snd::rest);
has type 'a list -> 'a, but don't have to declare it!
Also need to distinguish "equality" types:
- fun search item [] = false = | search item (fst::rest) = if item = fst then true = else search item rest; val search = fn : ''a -> ((''a list) -> bool)Double quote before variable name indicates "equality" type. Cannot use "=" on types which are real or function types or contain real or function types. Also only type variables allowed in equality types are those with ''.
- val x = 3 * 3; val x = 9 : int; - 2 * x; val it = 18 : int
Can also give local declarations of function and variables.
- fun roots (a,b,c) = let val disc = Math.sqrt (b * b - 4.0 * a * c) = in = ((~b + disc)/(2.0*a),(~b - disc)/(2.0*a)) = end; - roots (1.0,5.0,6.0); (~2.0,~3.0) : real * real - disc; Type checking error in: disc Unbound value identifier: disc
- val x = 3; val x = 3 : int - fun f y = x + y; val f = fn : int -> int - val x = 6; val x = 6 : int - f 0;What is answer?
3!!Why? Because definition of f used first "x", not second.
ML employs "eager" or call-by-value parameter passing
Talk later about "lazy" or "call-by-need".
Can have sequential or parallel declarations:
- val x = 12 = val y = x +2; val x = 12 : int val y = 14 : int - val x = 2 = and y = x + 3; val x = 2 : int val y = 15 : int
In the first pair of declarations, the declared value of x is used in the definition of y. In the second pair, however, because both are given in parallel, both declarations use only declared values before the parallel declaration. Thus y is evaluated using the old value of x.
However, when defining functions, simultaneous declaration supports mutual recursion.
- fun f n = if n = 0 then 1 else g n = and g m = m * f(m-1);
fun partition (pivot, nil) = (nil,nil) | partition (pivot, first :: others) = let val (smalls, bigs) = partition(pivot, others) in if first < pivot then (first::smalls, bigs) else (smalls, first::bigs) end;The system responds with:
val partition = fn : int * int list -> int list * int listNot polymorphic since system assumes "<" is on integers. Can force to be function on reals (for example) by including type of pivot in declaration: (pivot:real,nil)
fun qsort nil = nil | qsort [singleton] = [singleton] | qsort (first::rest) = let val (smalls, bigs) = partition(first,rest) in qsort(smalls) @ [first] @ qsort(bigs) end;It's hard to believe quicksort could be so simple!
Can make quicksort polymorphic if pass in less than operator to both partition and qsort:
fun partition (pivot, nil) (lessThan) = (nil,nil) | partition (pivot, first :: others) (lessThan) = let val (smalls, bigs) = partition(pivot, others) (lessThan) in if (lessThan first pivot) then (first::smalls, bigs) else (smalls, first::bigs) end; > val partition = fn : ('a * ('b list)) -> (('b -> ('a -> bool)) -> (('b list) * ('b list))) fun qsort nil lessThan = nil | qsort [singleton] lessThan = [singleton] | qsort (first::rest) lessThan = let val (smalls, bigs) = partition(first,rest) lessThan in (qsort smalls lessThan) @ [first] @ (qsort bigs lessThan) end; > val qsort = fn : ('a list) -> (('a -> ('a -> bool)) -> ('a list))Now if define:
- intLt (x:int) (y:int) = x < y; - qsort [6,3,8,4,7,1] intLt; > val [1,3,4,6,7,8] : int listNote: could define
- val PIntLt :int * int -> bool = op <;but wrong type for what needed here (though it is trivial to rewrite partition to take this type of function)!
type point = int * int (* nullary *) type 'a pair = 'a * 'a (* unary *)
Types are disjoint unions (w/constructors as tags)
Support recursive type definitions!
Generative (support pattern matching as well)
- datatype color = Red | Green | Blue; datatype color = Blue | Green | Red con Red = Red : color con Green = Green : color con Blue = Blue : color
"con" stands for constructor.
Write constructor tags with capital letter as convention to distinguish from variables.
- datatype 'a tree = Niltree | Maketree of 'a * ('a tree) * ('a tree) datatype 'a tree = Maketree of 'a * ('a tree) * ('a tree) | Niltree con Niltree = Niltree : 'a tree con Maketree = fn : ('a * ('a tree) * ('a tree)) -> ('a tree)
Write binary search program using trees!
fun insert (new:int) Niltree = Maketree (new,Niltree,Niltree) | insert new (Maketree (root,l,r)) = if new < root then Maketree (root,(insert new l),r) else Maketree (root,l,(insert new r)) fun buildtree [] = Niltree | buildtree (fst :: rest) = insert fst (buildtree rest) fun find (elt:int) Niltree = false | find elt (Maketree (root,left,right)) = if elt = root then true else if elt < root then find elt left else find elt right (* elt > root *) fun bsearch elt list = find elt (buildtree list); - buildtree [8,3,6,8,3,4,9,10]; Maketree (10,Maketree (9,Maketree (4,Maketree (3,Niltree, Maketree (3,Niltree,Niltree)),Maketree (8,Maketree (6,Niltree,Niltree), Maketree (8,Niltree,Niltree))),Niltree), Niltree) : int tree - bsearch 4 [8,3,6,8,3,4,9,10]; true : bool - bsearch 7 [8,3,6,8,3,4,9,10]; false : bool fun sumtree Niltree = 0 | sumtree (Maketree(root,left,right)) = root + sumtree left + sumtree right;
Can also have any kind of tagged unions:
- datatype sum = IntTag of int | RealTag of real | ComplexTag of real * real;
Abstract data types - later