(* Spring 2011, CSE341 lecture 11 *)
(* 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 toString : rational -> string
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 toString : rational -> string
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 toString : rational -> string
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 toString r =
case r of
Whole i => Int.toString i
| Frac(a,b) => (Int.toString a) ^ "/" ^ (Int.toString b)
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_A (*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 toString 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 => Int.toString i
| Frac(a,b) => (Int.toString a) ^ "/" ^ (Int.toString b)
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 C*)=
struct
type rational = int*int
exception BadFrac
fun make_frac z =
let val (x,y) = z in
if x <= 0 orelse y <= 0
then raise BadFrac
else z
end
fun Whole i = (i,1)
fun add ((a,b),(c,d)) = (a*d + c*b, b*d)
fun toString (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
in
Int.toString num ^ (if denom=1
then ""
else "/" ^ (Int.toString denom))
end
end