CS 641 Lecture -*- Outline -*- Lecture adapted from Mads Tofte's "Four lectures on Standard ML" Edinburgh ECS-LFCS-89-73 * ML at a Glance ** history ML stands for Meta Language for LCF designers Robin Milner, et al. (pre 1979) adapted ideas from spec lang CLEAR (modules, signatures) and prog. lang HOPE (pattern matching) ** goals to be used for writing proof tactics, => higher order functions, type system, exceptions => expression language ** character *** functional expression based, first class functions *** not purely functional has references, assignment, statements, ... *** has static scoping (vs. older LISPs) *** facilities for programming in the large structures (like an Ada package) signatures (like package interface) functor (like generic package, only more general) ** interaction program is one or more declarations (terminated by semicolons) an example: heaps (priority queues) a heap is a binary tree of items e.g. (7 (11 (9 17 15))) s.t. for each item i, i <= to all items "below" i (fast access to minimal item, easy to insert and delete, e.g., heap sort) ** types and values consider heaps of integers first -------------- type item = int; (* operations needed on items *) fun leq(p: item, q: item): bool = p <= q; infix leq; fun max(p,q) = if p leq q then q else p and min(p,q) = if p leq q then p else q ; -------------- binary trees -------------- datatype tree = L of item | N of item * tree * tree; val t = N(7, L 11, N(9, L 17, L 15)); -------------- notes: recursion, constructors L and N application synatax: L 7, or L(7), or (L 7) N (9, L 17, L 15) (* where N applied to 3-tuple *) val produces a binding (not assignable), val t: tree = ... functions on trees defined by cases (using constructors) this resembles axioms (makes equational reasoning easier) --------------- fun top(L i) = i | top(N(i,_,_)) = i; --------------- notes: patterns L i and N(i,_,_), _ is a wildcard e.g., top t = 7 ** recursive functions -------------- fun depth(L _) = 1 | depth(N(i,l,r)) = 1 + max(depth l, depth r); depth t; fun isHeap(L _): bool = true | isHeap(N(i,l,r)) = i leq top l andalso i leq top r andalso isHeap l andalso isHeap r; -------------- note: definition by structural induction exercise: write size (number of items in tree) ** raising exceptions initHeap makes a heap with depth n for n >= 1 what to do if argument is <1? raise exception ------------ val initial = 0; exception InitHeap; fun initHeap n = if n < 1 then raise InitHeap else if n = 1 then L(initial) else let val t = initHeap(n - 1) in N(initial, t, t) end; fun paranoid_initHeap n = initHeap n handle InitHeap => L(0); ------------ note: exception constructors (names) and value constructors (e.g., function names) are in the same value space, hence above doesn't work if exception is named "initHeap" instead of InitHeap let expression (sugar for function application, as usual) for local bindings following is another example of recursive function def. replace(i,h) returns a pair (i',h') such that i' is the top of h, and h' is obtained from h by inserting i in place of the top of h ------------- fun replace(i,h) = (top h, insert(i,h)) and insert(i, L _) = L(i) | insert(i, N(_,l,r)) = if i leq min(top l, top r) then N(i,l,r) else if (top l) leq (top r) then N(top l, insert(i,l), r) else (* (top r) < min(i, top l) *) N(top r, l, insert(i,r)); val (out1, t1) = replace(10,t); t; val (out2, t2) = replace(20, t1); ------------- exercise: what are the values of out1, t1, t, out2, t2? val out1 = 7 : item val t1 = N (9, L 11, N (10, L 17, L 15)) : tree val it = N (7, L 11, N (9, L 17, L 15)) : tree (* i.e., t *) val out2 = 9 : item val t2 = N (10, L 11, N (15, L 17, L 20)) : tree ** structures a named environment these are not values (so have signatures, not types) ----------------------- structure C_Heap = struct type item = int; fun op leq(p: item, q: item): bool = p <= q; infix leq; fun max(p,q) = if p leq q then q else p and min(p,q) = if p leq q then p else q ; datatype tree = L of item | N of item * tree * tree; val t = N(7, L 11, N(9, L 17, L 15)); fun top(L i) = i | top(N(i,_,_)) = i; fun depth(L _) = 1 | depth(N(i,l,r)) = 1 + max(depth l, depth r); fun isHeap(L _): bool = true | isHeap(N(i,l,r)) = i leq top l andalso i leq top r andalso isHeap l andalso isHeap r; val initial = 0; exception InitHeap; fun initHeap n = if n < 1 then raise InitHeap else if n = 1 then L(initial) else let val t = initHeap(n - 1) in N(initial, t, t) end; fun replace(i,h) = (top h, insert(i,h)) and insert(i, L _) = L(i) | insert(i, N(_,l,r)) = if i leq min(top l, top r) then N(i,l,r) else if (top l) leq (top r) then N(top l, insert(i,l), r) else (* (top r) < min(i, top l) *) N(top r, l, insert(i,r)); end; (* C_Heap *) ----------------------- access, dot notation and long identifiers --------- val smallHeap = C_Heap.initHeap(1); C_Heap.replace(20, smallHeap); --------- ** signatures ---------------- signature C_HEAP = sig type item val leq : item * item -> bool infix leq val max : item * item -> item val min : item * item -> item datatype tree = L of item | N of item * tree * tree val t : tree val top : tree -> item val depth : tree -> item val isHeap : tree -> bool val initial : int exception InitHeap val initHeap : int -> tree val replace : item * tree -> item * tree val insert : item * tree -> tree end; (* C_HEAP *) ---------------- *** signature matching: each type in C_HEAP, there is a corresponding type in Heap, for each exception, an exception, for each value, a value with the given type *** coercion (abstraction): C_HEAP has more detail than necessary, prune signature to get cleaner interface; i.e., signature can *hide* parts of a structure --------------- signature HEAP = sig type item val leq : item * item -> bool type tree val top : tree -> item exception InitHeap val initHeap : int -> tree val replace : item * tree -> item * tree end; (* HEAP *) --------------- think of signature as (structural) specification of a structure doesn't specify behavior --------------- structure A_Heap : HEAP = C_Heap; --------------- now A_Heap.t is an error but A_Heap.replace(7, A_Heap.initHeap 2); works note: why is 7 accepted, how do we know it's an "item"? system finds out that A_Heap.item = int this is merely an abbreviation but HEAP does hide the constructors of tree... A_Heap.L 0 gives an error even A_Heap.replace(7, C_Heap.initHeap 2); works ** functor declaration heaps of other types? requirements of HEAP structures: -------------- signature ITEM = sig type item val leq: item * item -> bool val initial: item end; -------------- parameterized structure is called a functor ---------------- functor Heap(Item: ITEM): HEAP = struct type item = Item.item fun op leq(p: item, q: item): bool = Item.leq(p,q); infix leq; fun intmax(i: int, j: int) = if i <= j then i else j fun max(p,q) = if p leq q then q else p and min(p,q) = if p leq q then p else q ; datatype tree = L of item | N of item * tree * tree; fun top(L i) = i | top(N(i,_,_)) = i; fun depth(L _) = 1 | depth(N(i,l,r)) = 1 + intmax(depth l, depth r); fun isHeap(L _): bool = true | isHeap(N(i,l,r)) = i leq top l andalso i leq top r andalso isHeap l andalso isHeap r; exception InitHeap; fun initHeap n = if n < 1 then raise InitHeap else if n = 1 then L(Item.initial) else let val t = initHeap(n - 1) in N(Item.initial, t, t) end; fun replace(i,h) = (top h, insert(i,h)) and insert(i, L _) = L(i) | insert(i, N(_,l,r)) = if i leq min(top l, top r) then N(i,l,r) else if (top l) leq (top r) then N(top l, insert(i,l), r) else (* (top r) < min(i, top l) *) N(top r, l, insert(i,r)); end; (* Heap *) ----------------- note: Item is the parameter structure, HEAP is the result signature ** functor application can get different heaps by applying Heap to distinct arguments e.g., StringHeap ------------ structure StringItem = struct type item = string fun op leq(i:item, j:item) = (i <= j) val initial = " " end; structure StringHeap = Heap(StringItem); val (out1, t1) = StringHeap.replace("abe", StringHeap.initHeap(1)); val (out2, t2) = StringHeap.replace("man", t1); ------------ exercise: how to get an integer heap whose top is always maximal? ** Summary Standard ML has a core language and a modules language *** core language values (&functions), data types, type abbreviations, exceptions *** modules language structures, signatures, functors note: structures and functors are not values (no types); functors annot be arguments to functors (no signatures)