Build with an interface, not a representation.
Constructors and selectors let a program use rational numbers without spreading knowledge of their pair representation through every operation.
What does an abstraction barrier protect us from changing?
- Separate an abstract value from its representation
- Use constructors and selectors as the only boundary
- Recognize code that reaches through an abstraction
make-rat constructs a rational value. numer and denom select its parts. add-rat depends on those three operations, not on car, cdr, or the order in which the pair stores its fields.
The pair is useful machinery, but it is not the public idea. Keeping representation knowledge behind a small interface makes later changes local instead of contagious.
(begin
(define (make-rat n d) (cons n d))
(define (numer x) (car x))
(define (denom x) (cdr x))
(define (add-rat x y)
(make-rat (+ (* (numer x) (denom y))
(* (numer y) (denom x)))
(* (denom x) (denom y))))
(let ((answer (add-rat (make-rat 1 2) (make-rat 1 3))))
(/ (numer answer) (denom answer))))- Output
- —
- Value
- —
- Diagnostic
- —
The first program returns the exact rational value 5/6.
Follow calls to make-rat, numer, and denom. add-rat never needs a direct pair operation, so its process does not reveal the storage order.
Change the program before you read the hint.
Store the denominator first in make-rat. Change only numer and denom, then confirm that add-rat still returns 5/6.
Show one hint
If add-rat must change, some representation knowledge crossed the intended boundary.