|
CS 334
|
No loops in the language, all functions written via recursion and if.. then.. else:
- fun fact n = if n = 0 then 1 else n * fact (n-1);
unit, bool, int, real, strings, characters
unit has only one value: ()
bool includes true, false and operators: not, andalso, orelse
int includes positive and negative: ...,~2, ~1,0,1,2...
real of form 3.17, 2.4E17
string of form "my string" - \t = tab, \n = newline.
char type is new in sml97. Write as # followed by string of length one. Thus #"b" is the character b, while "b" is the string of length one containing only the character b.
If expression involves an overloaded operator (e.g., + , *, -), and no other clues as to what type the argument or result should be, used to get type-checking error:
- fun double x = x + x;
Type checking error in: (syntactic context unknown)
Unresolvable overloaded identifier: +
Definition cannot be found for the type: ('a * 'a) -> 'a
In SML97, assumes the argument must be int:
- fun double x = x+x; val double = fn : int -> int
Must put in types if want to be other than int function if there are no other clues to type inference.
Can include type info if like.
- fun succ (x:real) = x + 1.0;
or
- fun succ x : real = x + 1.0;
(which tells system that the result of the function is a real) or even
- fun succ (x:real) :real = x + 1.0;though in these cases don't need to because clue of using "1.0" tells compiler that you want real addition!
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
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 = 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
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