#lang racket ;; CSE 341 Fall 2011, Lecture 24 ;; Abstraction with dynamic types ;; This is one of several files. This file shows ;; ways we might achieve modularity without using ;; Racket's module system, to remind us that ;; local scope /does/ hide things, but Racket's ;; modules are more convenient and flexible. ;; recall from lecture 12 we want a library that ;; manipulates rational numbers and shows them only in reduced form. ;; We use hidden functions gcd and reduce to do this. ;; From lecture 12: ;;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 ;; intended invariants in all attempts: ;; * no negative denominators ;; * rationals kept in reduced form ;; This initial attempt hides gcd and reduce, but ;; but does /not/ hide the representation of rationals, ;; so clients could crate "bad" rationals. To protect ;; against this, add and print-rat check invariants (struct rat1 (num den)) (define rat-funs1 (letrec ([gcd (lambda (x y) (cond [(= x y) x] [(< x y) (gcd x (- y x))] [#t (gcd y x)]))] [reduce (lambda (x y) (if (= x 0) (rat1 0 1) (let ([d (gcd (abs x) y)]) (rat1 (quotient x d) (quotient y d)))))] [make-frac (lambda (x y) (cond [(= y 0) (error "make-frac: 0 denominator")] [(< y 0) (reduce (- x) (- y))] [#t (reduce x y)]))] [add (lambda (r1 r2) (check-rat r1) ; excessive checks: add doesn't need reduced (check-rat r2) (let ([a (rat1-num r1)][b (rat1-den r1)] [c (rat1-num r2)][d (rat1-den r2)]) (reduce (+ (* a d) (* b c))(* b d))))] [print-rat (lambda (r) (check-rat r) (write (rat1-num r)) (unless (or (= (rat1-num r) 0) (= (rat1-den r) 1)) (write '/) (write (rat1-den r))))] [check-rat (lambda (r) (unless (rat1? r) (error "non-rational provided")) (let ([x (rat1-num r)][y (rat1-den r)]) (unless (and (integer? x) (integer? y) (> y 0) (= 1 (gcd (abs x) y))) (error "invariants violated"))))]) (list make-frac add print-rat))) ;; this list approach is Scheme-y -- Racket also has hashes built-in (define make-frac1 (car rat-funs1)) (define add1 (cadr rat-funs1)) (define print-rat1 (caddr rat-funs1)) ;; second approach: put the struct definition /inside/ the definition ;; doing so it like a letrec with a struct as part of it and we can ;; selectively export only some of the struct's functions, crucially ;; not exporting the constructor. (For this to work, we must have ;; a /new/ dynamic type -- would not work if struct were sugar for ;; cons cells (it's not).) ;; ;; since invariants are satisfied of all rationals, check-rat is much simpler (define rat-funs2 (let () (struct rat1 (num den)) (define (gcd x y) (cond [(= x y) x] [(< x y) (gcd x (- y x))] [#t (gcd y x)])) (define (reduce x y) (if (= x 0) (rat1 0 1) (let ([d (gcd (abs x) y)]) (rat1 (quotient x d) (quotient y d))))) (define (make-frac x y) (cond [(= y 0) (error "make-frac: 0 denominator")] [(< y 0) (reduce (- x) (- y))] [#t (reduce x y)])) (define (add r1 r2) (check-rat r1) (check-rat r2) (let ([a (rat1-num r1)][b (rat1-den r1)] [c (rat1-num r2)][d (rat1-den r2)]) (reduce (+ (* a d) (* b c))(* b d)))) (define (print-rat r) (check-rat r) (write (rat1-num r)) (unless (or (= (rat1-num r) 0) (= (rat1-den r) 1)) (write '/) (write (rat1-den r)))) (define (check-rat r) (unless (rat1? r) (error "non-rational provided"))) (list make-frac add print-rat))) ;; this list approach is Scheme-y -- Racket also has hashes built-in (define make-frac2 (car rat-funs2)) (define add2 (cadr rat-funs2)) (define print-rat2 (caddr rat-funs2)) ;; but really in Racket we use modules and usually modules are associated ;; with files, so see lec24_rationals.rkt and lec24_rationals_client.rkt