灯下 登录
计算机科学 / SICP / subsection 中英对照

2.5.1 Generic Arithmetic Operations

The task of designing generic arithmetic operations is analogous to that of

designing the generic complex-number operations. We would like, for instance,

to have a generic addition procedure add that acts like ordinary

primitive addition + on ordinary numbers, like add-rat on

rational numbers, and like add-complex on complex numbers. We can

implement add, and the other generic arithmetic operations, by following

the same strategy we used in 2.4.3 to implement the generic

selectors for complex numbers. We will attach a type tag to each kind of

number and cause the generic procedure to dispatch to an appropriate package

according to the data type of its arguments.

The generic arithmetic procedures are defined as follows:

设计通用算术操作的任务与设计通用复数操作类似。例如,我们希望有一个通用的加法过程 `add`,它对普通数的行为如同基本加法 `+`,对有理数的行为如同 `add-rat`,对复数的行为如同 `add-complex`。我们可以按照 2.4.3 节实现复数通用选择函数时所用的相同策略来实现 `add` 及其他通用算术操作:为每类数附加一个类型标签,让通用过程根据参数的数据类型分派到相应的包。通用算术过程的定义如下:

We begin by installing a package for handling

ordinary numbers, that

is, the primitive numbers of our language. We will tag these with the symbol

scheme-number. The arithmetic operations in this package are the

primitive arithmetic procedures (so there is no need to define extra procedures

to handle the untagged numbers). Since these operations each take two

arguments, they are installed in the table keyed by the list

(scheme-number scheme-number):

我们首先安装一个处理普通数的包,即语言中基本的数。我们用符号 `scheme-number` 为它们打标签。该包中的算术操作就是基本算术过程(因此无需定义额外的过程来处理无标签的数)。由于这些操作各接受两个参数,它们以 `(scheme-number scheme-number)` 为键安装到表中:

Users of the Scheme-number package will create (tagged) ordinary numbers by

means of the procedure:

`scheme-number` 包的用户将通过以下过程创建(带标签的)普通数:

Now that the framework of the generic arithmetic system is in place, we can

readily include new kinds of numbers. Here is a package that performs rational

arithmetic. Notice that, as a benefit of additivity, we can use without

modification the rational-number code from 2.1.1 as the internal

procedures in the package:

既然通用算术系统的框架已经就绪,我们便可以方便地纳入新的数类型。以下是一个执行有理数算术的包。注意,得益于可加性,我们可以直接使用 2.1.1 节中的有理数代码,无需修改,将其作为包的内部过程:

We can install a similar package to handle complex numbers, using the tag

complex. In creating the package, we extract from the table the

operations make-from-real-imag and make-from-mag-ang that were

defined by the rectangular and polar packages. Additivity permits us to use,

as the internal operations, the same add-complex, sub-complex,

mul-complex, and div-complex procedures from 2.4.1.

我们可以安装一个类似的包来处理复数,使用标签 `complex`。在创建该包时,我们从表中提取由直角坐标包和极坐标包定义的操作 `make-from-real-imag` 和 `make-from-mag-ang`。可加性允许我们使用 2.4.1 节中的 `add-complex`、`sub-complex`、`mul-complex` 和 `div-complex` 过程作为内部操作。

Programs outside the complex-number package can construct complex numbers

either from real and imaginary parts or from magnitudes and angles. Notice how

the underlying procedures, originally defined in the rectangular and polar

packages, are exported to the complex package, and exported from there to the

outside world.

复数包外部的程序可以用实部与虚部或模与辐角来构造复数。注意,最初在直角坐标包和极坐标包中定义的底层过程,是如何被导出到复数包,又从复数包导出到外部世界的。

What we have here is a two-level tag system. A typical complex number, such as

3

+

4

i in rectangular form, would be represented as shown in Figure 2.24.

The outer tag (complex) is used to direct the number to the

complex package. Once within the complex package, the next tag

(rectangular) is used to direct the number to the rectangular package.

In a large and complicated system there might be many levels, each interfaced

with the next by means of generic operations. As a data object is passed

“downward,” the outer tag that is used to direct it to the appropriate

package is stripped off (by applying contents) and the next level of tag

(if any) becomes visible to be used for further dispatching.

Figure 2.24: Representation of 3

+

4

i in rectangular form.

我们现在有了一个两级标签系统。一个典型的复数,例如直角坐标形式的 3 + 4i,其表示如图 2.24 所示。外层标签 `(complex)` 用于将该数引导至复数包;进入复数包后,下一层标签 `(rectangular)` 用于将其引导至直角坐标包。在大型复杂系统中,可能存在许多层级,每一层与下一层之间都通过通用操作相衔接。当一个数据对象向“下”传递时,用于将其引导至相应包的外层标签会被剥去(通过调用 `contents`),下一层标签(若有)便会显露出来,供进一步分派使用。

图 2.24:3 + 4i 的直角坐标形式表示。

In the above packages, we used add-rat, add-complex, and the

other arithmetic procedures exactly as originally written. Once these

definitions are internal to different installation procedures, however, they no

longer need names that are distinct from each other: we could simply name them

add, sub, mul, and div in both packages.

在上述各包中,我们完全按照最初编写的形式使用了 `add-rat`、`add-complex` 以及其他算术过程。然而,一旦这些定义都位于各自独立的安装过程内部,它们就不再需要彼此不同的名字:在两个包中,我们都可以简单地将它们命名为 `add`、`sub`、`mul` 和 `div`。

Racket #lang sicp
(define (add x y) (apply-generic 'add x y))
(define (sub x y) (apply-generic 'sub x y))
(define (mul x y) (apply-generic 'mul x y))
(define (div x y) (apply-generic 'div x y))
Racket #lang sicp
(define (install-scheme-number-package)
 (define (tag x)
 (attach-tag 'scheme-number x))
 (put 'add '(scheme-number scheme-number)
 (lambda (x y) (tag (+ x y))))
 (put 'sub '(scheme-number scheme-number)
 (lambda (x y) (tag (- x y))))
 (put 'mul '(scheme-number scheme-number)
 (lambda (x y) (tag (* x y))))
 (put 'div '(scheme-number scheme-number)
 (lambda (x y) (tag (/ x y))))
 (put 'make 'scheme-number
 (lambda (x) (tag x)))
 'done)
Racket #lang sicp
(define (make-scheme-number n)
 ((get 'make 'scheme-number) n))
Racket #lang sicp
(define (install-rational-package)
 ;; internal procedures
 (define (numer x) (car x))
 (define (denom x) (cdr x))
 (define (make-rat n d)
 (let ((g (gcd n d)))
 (cons (/ n g) (/ d g))))
 (define (add-rat x y)
 (make-rat (+ (* (numer x) (denom y))
 (* (numer y) (denom x)))
 (* (denom x) (denom y))))
 (define (sub-rat x y)
 (make-rat (- (* (numer x) (denom y))
 (* (numer y) (denom x)))
 (* (denom x) (denom y))))
 (define (mul-rat x y)
 (make-rat (* (numer x) (numer y))
 (* (denom x) (denom y))))
 (define (div-rat x y)
 (make-rat (* (numer x) (denom y))
 (* (denom x) (numer y))))
 ;; interface to rest of the system
 (define (tag x) (attach-tag 'rational x))
 (put 'add '(rational rational)
 (lambda (x y) (tag (add-rat x y))))
 (put 'sub '(rational rational)
 (lambda (x y) (tag (sub-rat x y))))
 (put 'mul '(rational rational)
 (lambda (x y) (tag (mul-rat x y))))
 (put 'div '(rational rational)
 (lambda (x y) (tag (div-rat x y))))
 (put 'make 'rational
 (lambda (n d) (tag (make-rat n d))))
 'done)

(define (make-rational n d)
 ((get 'make 'rational) n d))
Racket #lang sicp
(define (install-complex-package)
 ;; imported procedures from rectangular
 ;; and polar packages
 (define (make-from-real-imag x y)
 ((get 'make-from-real-imag
 'rectangular)
 x y))
 (define (make-from-mag-ang r a)
 ((get 'make-from-mag-ang 'polar)
 r a))
 ;; internal procedures
 (define (add-complex z1 z2)
 (make-from-real-imag
 (+ (real-part z1) (real-part z2))
 (+ (imag-part z1) (imag-part z2))))
 (define (sub-complex z1 z2)
 (make-from-real-imag
 (- (real-part z1) (real-part z2))
 (- (imag-part z1) (imag-part z2))))
 (define (mul-complex z1 z2)
 (make-from-mag-ang
 (* (magnitude z1) (magnitude z2))
 (+ (angle z1) (angle z2))))
 (define (div-complex z1 z2)
 (make-from-mag-ang
 (/ (magnitude z1) (magnitude z2))
 (- (angle z1) (angle z2))))
 ;; interface to rest of the system
 (define (tag z) (attach-tag 'complex z))
 (put 'add '(complex complex)
 (lambda (z1 z2)
 (tag (add-complex z1 z2))))
 (put 'sub '(complex complex)
 (lambda (z1 z2)
 (tag (sub-complex z1 z2))))
 (put 'mul '(complex complex)
 (lambda (z1 z2)
 (tag (mul-complex z1 z2))))
 (put 'div '(complex complex)
 (lambda (z1 z2)
 (tag (div-complex z1 z2))))
 (put 'make-from-real-imag 'complex
 (lambda (x y)
 (tag (make-from-real-imag x y))))
 (put 'make-from-mag-ang 'complex
 (lambda (r a)
 (tag (make-from-mag-ang r a))))
 'done)
Racket #lang sicp
(define (make-complex-from-real-imag x y)
 ((get 'make-from-real-imag 'complex) x y))
(define (make-complex-from-mag-ang r a)
 ((get 'make-from-mag-ang 'complex) r a))