(These notes may be updated soon.)
signature FRACTION = sig (* A fraction is a rational number *) type fraction (* first argument is numerator, second is denominator *) val make : int -> int -> fraction val numerator : fraction -> int val denominator : fraction -> int val toString : fraction -> string val toReal : fraction -> real val add : fraction -> fraction -> fraction val mul : fraction -> fraction -> fraction end (* Here's one implementation of fractions -- what can go wrong here? *) structure Fraction1 :> FRACTION = struct type fraction = { num:int, denom:int } fun make (n:int) (d:int) = {num=n, denom=d} fun numerator(x:fraction):int = #num x fun denominator(x:fraction):int = #denom x fun toString(x:fraction):string = (Int.toString (numerator x)) ^ "/" ^ (Int.toString (denominator x)) fun toReal(x:fraction):real = (Real.fromInt (numerator x)) / (Real.fromInt (denominator x)) fun mul (x:fraction) (y:fraction) : fraction = make ((numerator x)*(numerator y)) ((denominator x)*(denominator y)) fun add (x:fraction) (y:fraction) : fraction = make ((numerator x)*(denominator y) + (numerator y)*(denominator x)) ((denominator x)*(denominator y)) end
(* First, we could give 0 as the denominator -- this is a bad fraction. * Second, we're not reducing to smallest form. So we could overflow * faster than we need to. * Third, we're not consistent with the signs of the numbers. Try * make ~1 ~1. * * We need to pick some representation invariant * that describes how we're going to represent legal fractions. * Here is one choice that tries to fix the bugs above. *) structure Fraction2 :> FRACTION = struct (* Rep invariant: * (1) denom is always positive * (2) always in most reduced form *) type fraction = { num:int, denom:int } (* Algorithm due to Euclid: for positive numbers x and y, * find the greatest-common-divisor.*) fun gcd (x:int) (y:int) : int = if (x = y) then x else if (x < y) then gcd x (y - x) else gcd (x - y) y exception BadDenominator fun make (n:int) (d:int) : fraction = if (d = 0) then raise BadDenominator else let val g = gcd (abs n) (abs d) val n2 = n div g val d2 = d div g in if (d2 < 0) then {num = ~n2, denom = ~d2} else {num = n2, denom = d2} end fun numerator(x:fraction):int = #num x fun denominator(x:fraction):int = #denom x fun toString(x:fraction):string = (Int.toString (numerator x)) ^ "/" ^ (Int.toString (denominator x)) fun toReal(x:fraction):real = (Real.fromInt (numerator x)) / (Real.fromInt (denominator x)) (* notice that we didn't have to re-code mul or add -- * they automatically get reduced because we called * make instead of building the data structure directly. *) fun mul (x:fraction) (y:fraction) : fraction = make ((numerator x)*(numerator y)) ((denominator x)*(denominator y)) fun add (x:fraction) (y:fraction) : fraction = make ((numerator x)*(denominator y) + (numerator y)*(denominator x)) ((denominator x)*(denominator y)) end
(* Here's a signature for dictionaries. A dictionary is a container * that holds keys and associated objects. These dictionaries are * polymorphic (i.e., we can use them for any type.) Here we've * defined keys to be strings. *) signature DICTIONARY = sig type key = string type 'a dict (* make an empty dictionary carrying 'a values *) val make : unit -> 'a dict (* insert a key and value into the dictionary *) val insert : 'a dict -> key -> 'a -> 'a dict (* lookup a key in the dictionary -- raise NotFound if the * key is not present. *) val lookup : 'a dict -> key -> 'a exception NotFound end (* One implementation: an association list [(key1,x1),...,(keyn,xn)] *) structure AssocList :> DICTIONARY = struct type key = string type 'a dict = (key * 'a) list fun make():'a dict = [] fun insert (d:'a dict) (k:key) (x:'a) : 'a dict = (k,x)::d exception NotFound fun lookup (d:'a dict) (k:key) : 'a = case d of [] => raise NotFound | ((k',x)::rest) => if (k = k') then x else lookup rest k end (* This implementation seems a little better for looking up values *) structure SortedAssocList :> DICTIONARY = struct type key = string (* rep invariant: the list is sorted by key and * each key occurs only once in the list. *) type 'a dict = (key * 'a) list fun make():'a dict = [] fun insert (d:'a dict) (k:key) (x:'a) : 'a dict = case d of [] => (k,x)::nil | (k',x')::rest => (case String.compare(k,k') of GREATER => (k',x')::(insert rest k x) | EQUAL => (k,x)::rest | LESS => (k,x)::(k',x')::rest) exception NotFound fun lookup (d:'a dict) (k:key) : 'a = case d of [] => raise NotFound | ((k',x)::rest) => (case String.compare(k,k') of EQUAL => x | LESS => raise NotFound | GREATER => lookup rest k) end (* This one uses a binary tree to keep the data -- the hope is * that inserts or lookups will be proportional to log(n) where * n is the number of items in the tree. *) structure AssocTree :> DICTIONARY = struct type key = string (* Invariant: for Nodes, data to the left have keys that * are LESS than the datum and the keys of * the data to the right. *) datatype 'a dict = Empty | Node of {key: key,datum: 'a, left: 'a dict,right: 'a dict} fun make():'a dict = Empty fun insert (d:'a dict) (k:key) (x:'a) : 'a dict = case d of Empty => Node{key=k, datum=x, left=Empty, right=Empty} | Node {key=k', datum=x', left=l, right=r} => (case String.compare(k,k') of EQUAL => Node{key=k, datum=x, left=l, right=r} | LESS => Node{key=k',datum=x',left=insert l k x, right=r} | RIGHT => Node{key=k',datum=x',left=l, right=insert r k x}) exception NotFound fun lookup (d:'a dict) (k:key) : 'a = case d of Empty => raise NotFound | Node{key=k',datum=x, left=l, right=r} => (case String.compare(k,k') of EQUAL => x | LESS => lookup l k | RIGHT => lookup r k) end