(* Lecture 14: Modules and Abstract Types *)
(* this signature hides gcd and reduce.
That way clients cannot assume they exist or
call them with inputs that cause infinite loops. *)
signature RATIONAL_A =
sig
datatype rational = Frac of int * int | Whole of int
exception BadFrac
val make_frac : int * int -> rational
val add : rational * rational -> rational
val print_rat : rational -> unit
end
(* the previous signature lets clients build
any value of type rational they
want by exposing the Frac and Whole constructors.
This makes it impossible to maintain invariants
about rationals, so add may go in an infinite loop
and print_rat may print a non-reduced fraction.
We fix this by making rational abstract. *)
signature RATIONAL_B =
sig
type rational (* type now abstract *)
exception BadFrac
val make_frac : int * int -> rational
val add : rational * rational -> rational
val print_rat : rational -> unit
end
(* if we supported nonpositive numbers,
but still wanted the invariant that all fractions
were reduced, we could expose the Whole constructor
*)
signature RATIONAL_C =
sig
type rational (* type still abstract *)
exception BadFrac
val Whole : int -> rational
(* client knows only that Whole is a function *)
val make_frac : int * int -> rational
val add : rational * rational -> rational
val print_rat : rational -> unit
end
(* this structure provides a small library
for positive rational numbers. *)
structure PosRat1 :> RATIONAL_A (*or B or C*) =
struct
(* Invariant: no negative numbers and in
reduced form *)
datatype rational = Whole of int | Frac of int*int
exception BadFrac
(* gcd and reduce help keep fractions reduced,
but clients need not know about them *)
(* they _assume_ their inputs are not negative *)
fun gcd (x,y) =
if x=y
then x
else if x < y
then gcd(x,y-x)
else gcd(y,x)
fun reduce r =
case r of
Whole _ => r
| Frac(x,y) =>
let val d = gcd(x,y) in
if d=y
then Whole(x div d)
else Frac(x div d, y div d)
end
(* when making a frac, we ban nonpositive parts *)
fun make_frac (x,y) =
if x <= 0 orelse y <= 0
then raise BadFrac
else reduce (Frac(x,y))
(*next two functions _assume_ legal,reduced inputs*)
(* using math properties, the result of adding
is reduced and has no nonpositive parts *)
fun add (r1,r2) =
case (r1,r2) of
(Whole(i),Whole(j)) => Whole(i+j)
| (Whole(i),Frac(j,k)) => Frac(j+k*i,k)
| (Frac(j,k),Whole(i)) => Frac(j+k*i,k)
| (Frac(a,b),Frac(c,d)) =>
reduce (Frac(a*d + b*c, b*d))
fun print_rat r =
case r of
Whole i => print (Int.toString i)
| Frac(a,b) =>
let val _ = print (Int.toString a)
val _ = print "/"
val _ = print (Int.toString b)
in
()
end
end
(* By using signatures, you can enforce what
assumptions clients make.
That lets you replace structures that are
"equivalent from the client's perspective".
Here are two other structures that are
equivalent under signatures RATIONAL_B
and RATIONAL_C, but not under RATIONAL_A *)
(*this one doesn't reduce fractions until printing*)
(* under RATIONAL_A, clients can "see" fractions
are not reduced *)
structure PosRat2 :> RATIONAL_B (*or B or C*) =
struct
datatype rational = Whole of int | Frac of int*int
exception BadFrac
fun make_frac (x,y) =
if x <= 0 orelse y <= 0
then raise BadFrac
else Frac(x,y)
fun add (r1,r2) =
case (r1,r2) of
(Whole(i),Whole(j)) => Whole(i+j)
| (Whole(i),Frac(j,k)) => Frac(j+k*i,k)
| (Frac(j,k),Whole(i)) => Frac(j+k*i,k)
| (Frac(a,b),Frac(c,d)) =>
Frac(a*d + b*c, b*d)
fun print_rat r =
let fun gcd (x,y) =
if x=y
then x
else if x < y
then gcd(x,y-x)
else gcd(y,x)
fun reduce r =
case r of
Whole _ => r
| Frac(x,y) =>
let val d = gcd(x,y) in
if d=y
then Whole(x div d)
else Frac(x div d, y div d)
end
in
case reduce r of
Whole i => print (Int.toString i)
| Frac(a,b) =>
let val _ = print (Int.toString a)
val _ = print "/"
val _ = print (Int.toString b)
in
()
end
end
end
(* this structure uses a different abstract type.
It does not even have signature RATIONAL_A.
For RATIONAL_C, we need a function Whole. *)
structure PosRat3 :> RATIONAL_B_ (*(*or B or C*)*)=
struct
type rational = int*int
exception BadFrac
fun make_frac x = x (* the identity function *)
fun Whole i = (i,1)
fun add ((a,b),(c,d)) = (a*d + c*b, b*d)
fun print_rat (x,y) =
let fun gcd (x,y) =
if x=y
then x
else if x < y
then gcd(x,y-x)
else gcd(y,x)
val d = gcd (x,y)
val num = x div d
val denom = y div d
val _ = print (Int.toString num)
in
if denom=1
then ()
else
let val _ = print "/"
val _ = print(Int.toString denom)
in ()
end
end
end