Princeton University |
Computer Science 441 |
Ex.: If define
- fun updatehd newhd (head::tail) = newhd :: tail;then get sharing:

Safe, because list elt's not updatable!
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)!
Ex. Obvious recursive def in ML:
- fun fib 0 : int = 1
| fib 1 = 1
| fib n = fib (n-2) + fib (n-1);
Iterative
solution in Pascal - faster!
Function fastfib (n:integer):integer;
val a,b : integer;
begin
a := 1; b := 1;
while n > 0 do
begin
a := b; b := a + b; n := n-1 (* all done in parallel, else wrong! *)
end;
fib := a
end;
ML equivalent
fun fastfib n = let
fun fibLoop a b 0 = a
| fibLoop a b n : int = fibLoop b (a+b) (n-1)
in
fibLoop 1 1 n
end;
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
Order of operations:
Ex.
- fun test (x:{a:int,b:unit}) =
if (#a{a=2,b=print("A\n")} = 2)
then (#a x)
else (#a x);
val test = fn : { a:int, b:unit } -> int
- test {a = 7, b = print("B")};
If
have eager evaluation, get:
BA
val it = 7 : int
If
have lazy evaluation, get:
val it = 7 : int
Call-by-need
is equivalent to call-by-name (see discussion of parameter passing
techniques later in course ) in functional languages, but can be
implemented more efficiently since when evaluate argument, can save value
(since it won't change). Can also share different instances of parameter.
E.g.,
fun multiple x = if x = [1,2,3] then 0::x else x@[4,5,6]
When
substitute in value for x, don't really need to make three copies (again, since
can't change!)Lazy evaluation allows programmer to create infinite lists.
Ex. (in lazy dialect of ML)
fun from n = n :: from (n+1)
val nats = from 1;
fun nth 1 (fst::rest) = fst
| nth n (fst::rest) = nth (n-1) rest
Can
get approximate square root of x by starting with an approximation a0 for sqrt
and then getting successive approximations by calculatingan+1 = 1/2 * (an + x/an)
Program infinite list of approximations by:
fun approxsqrts x =
let
from approx = approx :: from (0.5 * (approx + x/approx))
in
from 1.0
end;
If want approximation where difference between successive approximation is <
eps,
fun within eps (approx1 :: approx2 :: rest) =
if abs(approx1 - approx2) < eps
then approx1
else absolute eps (approx2::rest);
Now
to get square root approximation in which the difference between
successive terms is < eps
then write:
fun sqrtapprox x eps = within eps (approxsqrts x)
Of
course can also do with eager language, but bit more to worry about - must
combine logic of both approxsqrts and within into same function.
If language has side-effects, then important to know when they will occur!
Also many-optimizations involve introducing side-effects into storage to save time.
In parallelizing computation, often better to start computation as soon as ready. With eager evaluation, evaluation of parameter may be wasted.
Can simulate lazy evaluation in eager language by making expressions into "parameterless" functions.
I.e., if wish to delay evaluation of E : T, change to fn : () => E of type unit -> T.
Ex: Spose wish to implement second parameter of f with lazy evaluation:
f x y = if x = [] then [] else x @ y
Rewrite
as
f' x y' = if x = [] then [] else x @ (y' ()) (* note y' applied to element of type unit *)
If
would normally write:f E1 E2, instead write f' E1 (fn () => E2)
Then E2 only evaluated if x != []!
Implement lazy lists, Suspended lists, in an eager language:
datatype 'a susplist = Mksl of (unit -> 'a * 'a susplist) | Endsl;
Like regular list, but must apply to () before get components!
(* add new elt to beginning of suspended list *)
fun slCons newhd slist = let fun f () = (newhd, slist)
in Mksl f end;
exception empty_list;
(* extract head of suspended list *)
fun slHd Endsl = raise empty_list
| slHd (Mksl f) = let val (a,s) = f ()
in a end;
(* extract tail of suspended list *)
fun slTl Endsl = raise empty_list
| slTl (Mksl f) = let val (a,s) = f()
in s end;
(* Is suspended list empty? *)
fun slNull Endsl = true
| slNull(Mksl f) = false;
(* Infinite list of ones as suspended list *)
val ones = let fun f() = (1,Mksl f)
in Mksl f end;
(* Suspended list of increasing integers starting with n *)
fun from n = let fun f() = (n, from(n+1))
in Mksl f end;
val nat = from 1;
Languages like LISP and SCHEME as well as lazy languages support streams for I/O.