Suppose we want to do
arithmetic with rational numbers. We want to be
able to add, subtract, multiply, and divide them and to test whether
two rational numbers are equal.
Let us begin by assuming that we already have a way of constructing a
rational number from a numerator and a denominator. We also assume
that, given a rational number, we have a way of extracting (or
selecting) its numerator and its denominator. Let us further assume
that the constructor and selectors are available as
functions:
make_rat($n$, $d$)
returns the
rational number whose numerator is the integer
$n$ and whose denominator is the integer
$d$.
numer($x$)
returns the numerator of the rational number
$x$.
denom($x$)
returns the denominator of the rational number
$x$.
We are using here a powerful strategy of synthesis:
wishful thinking. We haven't yet said how a rational number
is represented, or how the
functions
numer, denom, and
make_rat
should be implemented. Even so, if we did have these three
functions,
we could then add, subtract, multiply, divide, and test equality by using
the following relations:
Now we have the operations on rational numbers defined in terms of the
selector and constructor
functions
numer, denom, and
make_rat.
But we haven't yet defined these. What we need is some way to glue
together a numerator and a denominator to form a rational number.
To enable us to implement the concrete level of our data abstraction, our
JavaScript environment
provides a compound structure called a
pair, which can be constructed with the
primitive function
pair.
This
function
takes two arguments and returns a compound data object that contains the
two arguments as parts. Given a pair, we can extract the parts using the
primitive
functions
head
and
tail.
Thus, we can use
pair,
head,
and
tail
as follows:
Notice that a pair is a data object that can be given a name and
manipulated, just like a primitive data object. Moreover,
pair
can be used to form pairs whose elements are pairs, and so on:
const x = pair(1, 2);
const y = pair(3, 4);
const z = pair(x, y);
head(head(z));
1
head(tail(z));
3
In section 2.2 we will see how this
ability to combine pairs means that pairs can be used as general-purpose
building blocks to create all sorts of complex data structures. The single
compound-data primitive pair, implemented by the
functions
pair,
head,
and
tail,
is the only glue we need. Data objects constructed from pairs are called
list-structured data.
Pairs offer a natural way to complete the
rational-number system.
Simply represent a rational number as a pair of two integers: a numerator
and a denominator. Then
make_rat,
numer, and denom
are readily implemented as follows:
function make_rat(n, d) { return pair(n, d); }
function numer(x) { return head(x); }
function denom(x) { return tail(x); }
Also, in order to display the results of our computations, we can
print rational numbers by printing the numerator, a slash, and the
また、計算結果を表示するために、分子、スラッシュ、そして
denominator.
We use the primitive function
stringify to turn any value (here
a number) into a string. The operator
+ in JavaScript is
overloaded; it can be applied to two numbers or to two strings,
and in the latter case it returns the result of concatenating
the two strings.
As the final example shows, our rational-number implementation does not
reduce rational numbers to lowest terms. We can remedy this by changing
make_rat.
If we have a
gcd
function
like the one in section 1.2.5 that produces
the greatest common divisor of two integers, we can use
gcd to reduce the numerator and the
denominator to lowest terms before constructing the pair:
function make_rat(n, d) {
const g = gcd(n, d);
return pair(n / g, d / g);
}
Now we have
これで次の結果が得られます。
print_rat(add_rat(one_third, one_third));
"2 / 3"
as desired. This modification was accomplished by changing the constructor
make_rat
without changing any of the
functions
(such as
add_rat
and
mul_rat)
that implement the actual operations.
Define a better version of
make_rat
that handles both positive and negative arguments.
The function make_rat
should normalize the sign so that if the rational number is positive, both
the numerator and denominator are positive, and if the rational number is
negative, only the numerator is negative.
The first definition associates the name
make_rat
with the value of the expression
pair,
which is the primitive
function
that constructs pairs. Thus
make_rat
and
pair
are names for the same primitive constructor.
Defining selectors and constructors in this way is efficient: Instead of
make_ratcallingpair,
make_ratispair,
so there is only one
function
called, not two, when
make_rat
is called. On the other hand, doing this defeats debugging aids that
trace
function
calls or put breakpoints on
function
calls:
You may want to watch
make_rat
being called, but you certainly don't want to watch every call to
pair.
We have chosen not to use this style of definition in this book.
In JavaScript, the operator
+ can also be applied to a string and a number
and to other operand combinations, but in this book,
we choose to apply it either to two numbers or to two strings.
The primitive function
display
introduced in exercise 1.22
returns its argument, but in the uses of
print_rat below, we show only what
print_rat prints, not what the interpreter
prints as the value returned by
print_rat.