for
CSCI 301: Great Ideas in Computer Science
(c) 1993, 1994, 1995 John E. Howland, All Rights Reserved
Department of Computer Science
Trinity University
Stadium Drive
San Antonio, Texas 78212-7200
Internet: jhowland@ariel.cs.trinity.edu
However, the production of software is so labor intensive (read expensive) that it is often rather expensive to prototype a design. Hence the prototyping phase is often omitted. Therefore, the customer may be forced to use a program on a production basis which is really nothing more than a prototype.
Design engineers often say that they don't fully know how to solve the problem until after they have produced a prototype. A corollary to this saying is that the first prototype design should be scrapped and the second prototype or final design should be built from the ground up using the experience gained from the first prototype.
If software design is often so expensive as to preclude prototyping as a standard practice, how then can one afford to do one or more prototype designs before building the production design? This is the challenging question and much research is being done on this question inorder to make software prototyping a more common practice.
What is needed are special prototyping languages or systems which are perhaps interactive systems containing a rich and powerful set of design and construction tools. Such programming environments should allow easy development of software layers from the top down and should facilitate the design of abstraction barriers between the software layers. These barriers are the interfaces which allow isolation of one design layer from another so that changes to a given layer can be made without requiring changes to the layers above and below. This is accomplished by making sure that communication with a layer above or below is always done using the interface to the layer, rather than being done in terms of the actual implementation of the layer.
Here is a diagram illustrating the idea of layering.

The interface between layers usually consists of a set of operations to construct, access and otherwise manipulate objects in the layer below.
In general, it is difficult to achieve a perfectly layered software design. The reasons for this are many.
Programming languages often don't support the degree of abstraction required to fully support layering. The interfaces between layers introduce an inherent inefficiency which may affect the overall efficiency of the software system being designed. In practice, the designer has to trade off efficiency for a properly layered design. In the final analysis efficiency often wins.
An object's enclosure tends to protect its contents (data items and functions to manipulate those data) from inappropriate use and helps insure that software is built in a modular fashion. If objects are defined in terms of other objects it may be possible (depending on the object programming language used) to allow objects to inherit data and operational behavior from other objects. This technique allows systematic reuse of data structure and programs which is useful when engineering software. These are some of the reasons why there is a lot of interest in object programming currently.
We wish to design a system which will perform rational number arithmetic. Recall that a rational number may be represented as a fraction or ratio of two integers a/b where, of course, b != 0. There are infinitely many fractions which represent the same rational number. For example, 4/8 and 5/10 are two different representations of the rational number one half which we usually write as 1/2.
If we introduce an abstract data type of rational-number then we have to define a set of objects, i.e. pairs of integers and a set of operations for rational numbers such as addition, subtraction, multiplication, division, etc.
A layered design for rational numbers might provide an interface which separates the layer where the representation of rational numbers is defined from the layer where the rational number operations are defined. This interface should specify a rational number constructor and accessors for the various parts of a rational number such as the numerator and denominator.
(make-rat numerator denominator) ==> rational-representation
(numer rational-number) ==> numerator (denom rational-number) ==> denominatorNotice at this point we have not given actual definitions of how rational numbers are to be represented. We have specified the interface operations only. Next we can define the rational number operations of addition, subtraction, multiplication and division. Recall that when adding or subtracting fractions one must have a common denominator and when multiplying or dividing one multiplies or divides numberators and denominators. Hence we have the following definitions for the rational number operations.
(define +rat
(lambda (x y)
(make-rat
(+ (* (numer x) (denom y))
(* (denom x) (numer y)))
(* (denom x) (denom y)))))
(define -rat
(lambda (x y)
(make-rat
(- (* (numer x) (denom y))
(* (denom x) (numer y)))
(* (denom x) (denom y)))))
8.3.5
Rational Number Multiplication
(define *rat
(lambda (x y)
(make-rat
(* (numer x) (numer y))
(* (denom x) (denom y)))))
(define /rat
(lambda (x y)
(make-rat
(* (numer x) (denom y))
(* (denom x) (numer y)))))
Of
course we could define many other rational number operations such as:
(define =rat
(lambda (x y)
(=
(* (numer x) (denom y))
(* (numer y) (denom x)))))
(define print-rat
(lambda (x)
(display (numer x))
(display "/")
(display (denom x))
(newline)))
Next
we define the lower level representation of a rational number n/d to be the
pair (n . d). Hence, we have:
(define make-rat (lambda (n d) (cons n d)))and rational number accessors
(define numer (lambda (x) (car x))) (define denom (lambda (x) (cdr x)))We now have enough software defined to begin testing. We can think of this as our first prototype for our rational number software package.
(print-rat (make-rat 3 4)) ==> 3/4
(print-rat (+rat (make-rat 3 4)
(make-rat 1 2))) ==> 10/8
(print-rat (/rat (make-rat 3 4)
(make-rat 1 2))) ==> 6/4
We could make use of the greatest common divisor function, gcd, and remove the greatest common divisor of the numerator and devisor from both the numerator and denominator. There are two possibilities; remove the common factors when the rational number is constructed or remove the common factors whenever a numerator or denominator is accessed.
(define make-rat
(lambda (n d)
(let ((g (gcd n d)))
(cons (/ n g) (/ d g)))))
Notice
that none of the rational number operations needs to be changed to make this
new prototype operational. This is one of the main benefits of the layered
approach to engineering software.
(print-rat (make-rat 3 4)) ==> 3/4
(print-rat (+rat (make-rat 3 4)
(make-rat 1 2))) ==> 5/4
(print-rat (/rat (make-rat 3 4)
(make-rat 1 2))) ==> 3/2
(define make-rat
(lambda (n d)
(cons n d)))
and
substitute the following definitions for number and denom.
Notice again that none of the rational number operations needs to be changed.
(define numer
(lambda (x)
(let ((g (gcd (car x) (cdr x))))
(/ (car x) g))))
(define denom
(lambda (x)
(let ((g (gcd (car x) (cdr x))))
(/ (cdr x) g))))
(print-rat (make-rat 3 4)) ==> 3/4
(print-rat (+rat (make-rat 3 4)
(make-rat 1 2))) ==> 5/4
(print-rat (/rat (make-rat 3 4)
(make-rat 1 2))) ==> 3/2
The actual choice of reduction method to be used in a design for production software may depend on performance requirements of the rational number operations. It may, therefore, be necessary to make performance experiments for each design.