Elements of ML Programming, 2nd Edition (ML97) |
structure Tree = struct exception Missing; datatype 'label tree = Node of 'label * 'label tree list; (* create a one-node tree *) fun create(a) = Node(a,nil); (* build a tree from a label and a list of trees *) fun build(a,L) = Node(a,L); (* find the ith subtree of a tree *) fun subtree(i,Node(a,nil)) = raise Missing | subtree(1,Node(a,t::ts)) = t | subtree(i,Node(a,t::ts)) = subtree(i-1,Node(a,ts)); end;
signature SIMPLE = sig exception Missing; datatype 'label tree = Node of 'label * 'label tree list; val build : int * int tree list -> int tree; val subtree : int * int tree -> int tree end;We can then use this signature to create the desired structure by
structure SimpleTree: SIMPLE = Tree;
open SimpleTree; val t2 = build(2,nil); val t3 = build(3,nil); val t1 = build(4,nil); val t4 = build(1,[t1,t2,t3]); subtree(2,t4);Download code for Exercises 8.2.1, 8.2.2, and 8.2.3.
structure Queue = struct exception EmptyQueue; type 'a queue = 'a list; val create = nil; fun enqueue(x,Q) = Q@[x]; fun dequeue(nil) = raise EmptyQueue | dequeue(q::qs) = (q,qs); fun isEmpty(nil) = true | isEmpty(_) = false; end;Note that we have written isEmpty as we did, rather than the more succinct fun isEmpty(Q) = (Q=nil), because by matching patterns we avoid the equality test and therefore allow the type of Q to be anything, even a non-equality type. Download this program
signature SIM = sig type element; val sim : element * element -> bool end; functor MakeSimSet(Sim: SIM): sig type set; val create : set; val insert : Sim.element * set -> set; val findSim : Sim.element * set -> set end = struct open Sim; type set = element list; val create = nil; fun insert(x,S) = x::S; fun findSim(x,nil) = nil | findSim(x,s::ss) = if sim(x,s) then s::findSim(x,ss) else findSim(x,ss) end; structure Misspell: SIM = struct type element = string; fun sim("","") = true | sim("",_) = false | sim(_,"") = false | sim(x,y) = let val xhead::xtail = explode(x); val yhead::ytail = explode(y); val xs = implode(xtail); val ys = implode(ytail) in if xhead=yhead then sim(xs,ys) else xs=ys end end; structure MisspellSet = MakeSimSet(Misspell);Note that the code for function sim in structure Misspell will produce a warning because ML thinks that when we explode x and y, one or both could result in the empty list, which would not match the patterns like xhead::xtail. However, we have caught such cases in the preceding patterns that check for one or both strings being empty.
structure Pair: ELEMENT = struct type element = int * int; fun similar((x1,x2), (y1,y2)) = (x1=y1) end;Then, the following is a sketch of a structure that uses Pair as its substructure called Element and defines the local type elt to be the same type as appears in structure Pair.
structure BinaryTree: BTREE = struct structure Element = Pair; type elt = int * int; (* suitable definitions for btree and the functions *) end;However, should we replace type elt = int * int in structure BinaryTree by any other type, for example type elt = real * int, then we would get an error indicating a violation of a sharing constraint.
Function lookup is straightforward. We use the separators to guide us to the proper leaf. Function insert requires a lot of work. The heart is in a function insert1 that finds the proper subtree into which the insertion occurs and returns a oneOrTwo, that is, either a single tree or a pair, wrapped appropriately. The five auxiliary functions group test whether there is a single or a pair and produce a wrapped tree or pair, as appropriate. The only time a pair is produced is in the case where three subtrees become four, as was illustrated in Fig. 8.18 of the text. Note that the five group functions are named with a code where U stands for ``unknown'' (we get either a single or a pair) and T (we get a single tree).
structure TTTree = struct datatype tttree = Two of int * tttree * tttree | Three of int * int * tttree * tttree * tttree | Leaf of int; datatype oneOrTwo = S of tttree | P of int * tttree * tttree; (* create(i) creates a 2-3 tree with one leaf, labeled i *) fun create(i) = Leaf(i); (* lookup(x,T) tells whether integer x is at a leaf of 2-3 tree T *) fun lookup(x,Leaf(i)) = (x=i) | lookup(x,Two(i,T1,T2)) = if x=j *) lookup(x,T3); (* the following 5 "group" functions are auxiliaries used in the function insert1 below. See explanation in the text. *) fun groupUT(i,S(T1),T2) = S(Two(i,T1,T2)) | groupUT(i,P(j,T1,T2),T3) = S(Three(j,i,T1,T2,T3)); fun groupTU(i,T1,S(T2)) = S(Two(i,T1,T2)) | groupTU(i,T1,P(j,T2,T3)) = S(Three(i,j,T1,T2,T3)); fun groupUTT(i,j,S(T1),T2,T3) = S(Three(i,j,T1,T2,T3)) | groupUTT(i,j,P(k,T1,T2),T3,T4) = P(i,Two(k,T1,T2),Two(j,T3,T4)); fun groupTUT(i,j,T1,S(T2),T3) = S(Three(i,j,T1,T2,T3)) | groupTUT(i,j,T1,P(k,T2,T3),T4) = P(k,Two(i,T1,T2),Two(j,T3,T4)); fun groupTTU(i,j,T1,T2,S(T3)) = S(Three(i,j,T1,T2,T3)) | groupTTU(i,j,T1,T2,P(k,T3,T4)) = P(j,Two(i,T1,T2),Two(k,T3,T4)); (* insert1(x,T) inserts integer x into 2-3 tree T and returns a oneOrTwo, that is, a 2-3 tree wrapped in S or two 2-3 trees wrapped in P *) fun insert1(x,Leaf(i)) = if xi then P(x,Leaf(i),Leaf(x)) else S(Leaf(i)) | insert1(x,Two(i,T1,T2)) = if x=i *) groupTU(i,T1,insert1(x,T2)) | insert1(x,Three(i,j,T1,T2,T3)) = if x=j *) groupTTU(i,j,T1,T2,insert1(x,T3)); (* unwrap(X) either removes the one tree from an S or creates a new node with the two subtrees found inside a P. *) fun unwrap(S(T)) = T | unwrap(P(i,T1,T2)) = Two(i,T1,T2); (* insert(x,T) inserts x into 2-3 tree T. *) fun insert(x,T) = unwrap(insert1(x,T)) end;Download this program
structure SafeHash: sig val create: unit -> string hashTable; val lookup: string hashTable * string -> bool; val insert: string hashTable * string -> unit; val delete: string hashTable * string -> unit; end = Hash;For the above to make sense, we need to assume that the structure Hash has a type 'a hashTable representing a hash table whose elements are of arbitrary type. If that were not the case, then the type of a hash table would probably be 'a list array.