(* CSE 341 Lecture 12 - Code examples *)
(* 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 it 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: have no negative numbers and are 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 have no negative parts *)
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 we make a fraction, we prohibit nonpositive parts *)
fun make_frac (x,y) =
if x <= 0 orelse y <= 0
then raise BadFrac
else reduce (Frac(x,y))
(* these next two functions _assume_ their inputs are legal and reduced *)
(* 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 structure does not reduce fractions until printing them *)
(* 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 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. Because of RATIONAL_C, we provide a
function Whole. *)
structure PosRat3 (* :> RATIONAL_A (* 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