![]() 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 : intIf have lazy evaluation, get:
val it = 7 : intCall-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) restCan get approximate square root of x by starting with an approximation a0 for sqrt and then getting successive approximations by calculating
an+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 @ yRewrite 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.