1) Environment model is in the shop. Will return Thursday or, at latest, Monday. 2) With great power comes great responsibility --Spiderman Refs give us flexibility at the cost of complexity. One way to address this is by defining representation invariants and abstraction functions to formalize your program's behavior. An abstraction function maps a data structure onto a real world or mathematical concept which your program deals with. For example: * type BigInt = int list A.F.: integers in the list represent digits in a possibly large integer; in x::xs, x is the least significant digit. * type fraction = int * int f = (x,y) represents the fraction x/y A representation invariant is a statement which will always be true about your data structures. For example: * binary trees: datatype int tree = NODE of int*tree*tree | NILL NODE (n,a,b) is a tree, if every int in a < n and every int in b > n and a and b are trees. NILL is a tree. * type fraction of int * int f = (x,y) is a valid fraction iff y >= 0 * BigInt for each integer x in the list, (0<=x<=0) and [u,v,w ... , 0] is not a valid list (I.E. the most significant digit is always non-zero). 3) Try to get the students to help come up with the data structures and algorithms needed to implement a queue with constant (big O, not amortized) enqueue and dequeue times. What are the RI and AF? (* Update in place, polymorphic queue * Jeff Vaughan * Revised March 12, 2003 *) signature QUEUE = sig (* A queue is an imperative fifo data structure. Elements are added using * the enqueue fucntion, and removed with dequeue. Dequeue removes elements * in the same order they were added. *) type 'a Queue (* new(x) returns an 'a Queue containing only x *) val new: 'a -> 'a Queue (* enqueue(q,x) imperatively adds x to the end of q. *) val enqueue: 'a Queue * 'a -> unit (* dequeue q returns SOME(x) where x is at the front of q, or NONE if * q is empty. If q is not empty the front element is removed. *) val dequeue: 'a Queue -> 'a option (* peek q returns the same value of deque q, but does not alter q *) val peek: 'a Queue -> 'a option end structure Queue :> QUEUE = struct (* Abstraction Function *) (* An abstract queue is an ordered set of elements. The empty queue * is represented by {head = ref NONE, tail = ref NONE}. * Non-empty queues are represented by * {head = ref (SOME h), tail = ref (SOME t)} where h and t are the head and * tail of a doubly linked list. Nodes are stored in insertion order, where * head of the list is the oldest element. *) (* Representation Invariant *) (* For every node except head and tail, !ahead = SOME(a) and * behind = !SOME(b). For head, !ahead = NONE. For tail, * !behind = NONE. head and tail need not be distinct. * !head = NONE iff !tail = NONE *) datatype 'a Node = Node of {data : 'a, ahead : 'a Node option ref, behind : 'a Node option ref} type 'a Queue = {head : 'a Node option ref, tail : 'a Node option ref} exception Empty fun new(x : 'a): 'a Queue = let val n = Node{ahead = ref NONE, data = x, behind = ref NONE} in {head = ref (SOME n), tail = ref (SOME n)} end fun enqueue({head, tail}: 'a Queue, x: 'a) = let val n = Node{ahead = tail, behind = ref NONE, data = x} val _ = case !tail of NONE => head := SOME n | SOME(Node{behind, ...}) => behind := SOME n val _ = tail := SOME n in () end fun peek({head, ...}: 'a Queue): 'a option = case !head of SOME (Node{data, ...}) => SOME data | NONE => NONE fun dequeue({head, tail}: 'a Queue): 'a option = case !head of NONE => NONE | SOME (Node{data,behind,...}) => let val _ = head := !behind val _ = (case !behind of NONE => tail := NONE | SOME (_) => () ) in SOME data end end