1.1.0 The Elements of Programming

1.1  The Elements of Programming
1.1  程序设计的基本元素

A powerful programming language is more than just a means for instructing a computer to perform tasks. The language also serves as a framework within which we organize our ideas about processes. Thus, when we describe a language, we should pay particular attention to the means that the language provides for combining simple ideas to form more complex ideas. Every powerful language has three mechanisms for accomplishing this:

一个强有力的程序设计语言,不仅是一种指挥计算机执行任务的方式,它还应该成为一种框架,使我们能够在其中组织自己有关计算过程的思想。这样,当我们描述一个语言时,就需要将注意力特别放在这一语言所提供的,能够将简单的认识组合起来形成更复杂认识的方法方面。每一种强有力的语言都为此提供了三种机制:
  • primitive expressions, which represent the simplest entities the language is concerned with
  •  基本表达形式,用于表示语言所关心的最简单的个体。
  • means of combination, by which compound elements are built from simpler ones, and
  •  组合的方法,通过它们可以从较简单的东西出发构造出复合的元素。
  • means of abstraction, by which compound elements can be named and manipulated as units.
  • 抽象的方法,通过它们可以为复合对象命名,并将它们当作单元去操作。
In programming, we deal with two kinds of elements: procedures and data. (Later we will discover that they are really not so distinct.) Informally, data is ``stuff'' that we want to manipulate, and procedures are descriptions of the rules for manipulating the data. Thus, any powerful programming language should be able to describe primitive data and primitive procedures and should have methods for combining and abstracting procedures and data.

在程序设计中,我们需要处理两类要素:过程和数据(以后读者将会发现,它们实际上并不是这样严格分离的)。非形式地说,数据是一种我们希望去操作的“东西”,而过程就是有关操作这些数据的规则的描述。这样,任何强有力的程序设计语言都必须能表述基本的数据和基本的过程,还需要提供对过程和数据进行组合和抽象的方法。

In this chapter we will deal only with simple numerical data so that we can focus on the rules for building procedures.4 In later chapters we will see that these same rules allow us to build procedures to manipulate compound data as well.

本章只处理简单的数值数据,这就使我们可以把注意力集中到过程构造的规则方面4。在随后几章里我们将会看到,用于构造过程的这些规则同样也可以用于操作各种数据。

1.1.1  Expressions
1.1.1  表达式

One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. Imagine that you are sitting at a computer terminal. You type an expression, and the interpreter responds by displaying the result of its evaluating that expression.

开始做程序设计,最简单方式就是去观看一些与 Lisp 方言与 Scheme 解释器交互的典型实例。设想你坐在一台计算机的终端前,用键盘输入了一个表达式,解释器的响应就是将它对这一表达式的求值结果显示出来。

One kind of primitive expression you might type is a number. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number

你可以键入的一种基本表达式就是数(更准确地说,你键入的是由数字组成的表达式,它表示的是以 10 作为基数的数)。如果你给 Lisp 一个数

486

the interpreter will respond by printing5

解释器的响应是打印出5。

486

Expressions representing numbers may be combined with an expression representing a primitive procedure (such as + or *) to form a compound expression that represents the application of the procedure to those numbers. For example:

可以用表示基本过程的表达形式(例如 + 或者 *),将表示数的表达式组合起来,形成复合表达式,以表示求要把有关过程应用于这些数。例如:

(+ 137 349)
486
(- 1000 334)
666
(* 5 99)
495
(/ 10 5)
2
(+ 2.7 10)
12.7

Expressions such as these, formed by delimiting a list of expressions within parentheses in order to denote procedure application, are called combinations. The leftmost element in the list is called the operator, and the other elements are called operands. The value of a combination is obtained by applying the procedure specified by the operator to the arguments that are the values of the operands.

像上面这样的表达式称为组合式,其构成方式就是用一对括号括起一些表达式,形成一个表,用于表示一个过程应用。在表里最左的元素称为运算符,其他元素都称为运算对象。要得到这种组合式的值,采用的方式就是将由运算符所刻画的过程应用于有关的实际参数,而所谓实际参数也就是那些运算对象的值。

The convention of placing the operator to the left of the operands is known as prefix notation, and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate procedures that may take an arbitrary number of arguments, as in the following examples:

将运算符放在所有运算对象左边,这种形式称为前缀表示。刚开始看到这种表示时会感到有些不习惯,因为它与常规数学表示差别很大。然而前缀表示也有一些优点,其中之一就是它完全适用于可能带有任意个实参的过程,例如在下面实例中的情况:

(+ 21 35 12 7)

75

(* 25 4 12)

1200

No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses.

在这里不会出现歧义,因为运算符总是最左边的元素,而整个表达式的范围也由括号界定。

A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested, that is, to have combinations whose elements are themselves combinations:

前缀表示的第二个优点是它可以直接扩充,允许出现组合式嵌套的情况,也就是说,允许组合式的元素本身又是组合式:

(+ (* 3 5) (- 10 6))

19

There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as

原则上讲,对于这种嵌套的深度,以及 Lisp 解释器可以求值的表达式的整体复杂性,都没有任何限制。倒是我们自己有可能被一些不很复杂的表达式搞糊涂,例如:

(+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6))

which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form

对于这个表达式,解释器可以马上求值出 57。将上述表达式写成下面的形式有助于阅读,

(+ (* 3

        (+ (* 2 4)

           (+ 3 5)))

     (+ (- 10 7)

        6))

following a formatting convention known as pretty-printing, in which each long combination is written so that the operands are aligned vertically. The resulting indentations display clearly the structure of the expression.6

这就是遵循一种称为美观打印的格式规则。按照这种规则,在写一个很长的组合式时,我们令其中的各个运算对象垂直对齐。这样排列的结果能很好地显示出表达式的结构6。

Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, evaluates the expression, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop. Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression.7

即使对于非常复杂的表达式,解释器也总是按同样的基本循环动作:从终端读入一个表达式,对这个表达式求值,而后打印出得到的结果。这种动作模式常常被人们说成是解释器运行在一个读入-求值-打印循环之中。请特别注意,在这里完全没有必要显式地去要求解释器打印表达式的值7。
1.1.2 Naming and the Environment
1.1.2 命名和环境

A critical aspect of a programming language is the means it provides for using names to refer to computational objects. We say that the name identifies a variable whose value is the object.

程序设计语言中一个必不可少的方面,就是它需要提供一种通过名字去使用计算对象的方式。我们将名字标识符称为变量,它的值也就是它所对应的那个对象。

In the Scheme dialect of Lisp, we name things with define. Typing

在 Lisp 方言 Scheme 里,给事物命名通过 define(定义)的方式完成,输入:

(define size 2)

causes the interpreter to associate the value 2 with the name size.8 Once the name size has been associated with the number 2, we can refer to the value 2 by name:

会导致解释器将值 2 与名字 size 相关联8。一旦名字 size 与 2 关联之后,我们就可以通过这个名字去引用值 2 了:

size

2

(* 5 size)

10

Here are further examples of the use of define:

下面是另外几个使用 define 的例子:

(define pi 3.14159)

(define radius 10)

(* pi (* radius radius))

314.159

(define circumference (* 2 pi radius))

circumference

62.8318

Define is our language's simplest means of abstraction, for it allows us to use simple names to refer to the results of compound operations, such as the circumference computed above. In general, computational objects may have very complex structures, and it would be extremely inconvenient to have to remember and repeat their details each time we want to use them. Indeed, complex programs are constructed by building, step by step, computational objects of increasing complexity. The interpreter makes this step-by-step program construction particularly convenient because name-object associations can be created incrementally in successive interactions. This feature encourages the incremental development and testing of programs and is largely responsible for the fact that a Lisp program usually consists of a large number of relatively simple procedures.

define 是我们所用的语言里简单的抽象方法,它允许我们用一个简单的名字去引用一个组合运算的结果,例如上面算出的 circumference。一般而言,计算得到的对象完全可以具有非常复杂的结构,如果每次需要使用它们时,都必须记住并重复地写出它们的细节,那将是极端不方便的事情。实际上,构造一个复杂的程序,也就是为了去一步步地创建出越来越复杂的计算性对象。解释器使这种逐步的程序构造过程变得非常方便,因为我们可以通过一系列交互式动作,逐步创建起所需要的名字-对象关联。这种特征鼓励人们采用递增的方式去开发和调试程序。在很大程度上,这一情况也出于另一个事实。那就是,一个 Lisp 程序通常是由一大批相对简单的过程组成的。

It should be clear that the possibility of associating values with symbols and later retrieving them means that the interpreter must maintain some sort of memory that keeps track of the name-object pairs. This memory is called the environment (more precisely the global environment, since we will see later that a computation may involve a number of different environments).9

应该看到,我们可以将值与符号关联,而后又能提取出这些值,这意味着解释器必须维护某种存储能力,以便 保持有关的名字-值对偶的轨迹。这种存储被称为环境(更精确地说,是全局环境,因为我们以后将看到,在一个计算过程中完全可能涉及若干不同环境9)。

1.1.3 Evaluating Combinations
1.1.3 组合式的求值

One of our goals in this chapter is to isolate issues about thinking procedurally. As a case in point, let us consider that, in evaluating combinations, the interpreter is itself following a procedure.

本章的一个目标,就是要把与过程性思维有关的各种问题隔离出来。现在让我们考虑组合式的求值问题。解释器本身就是按照下面过程工作的。

    * To evaluate a combination, do the following:
    * 要求值一个组合式,做下面的事情:

    1.  Evaluate the subexpressions of the combination.

    1.  求值该组合式的各个子表达式。

    2.  Apply the procedure that is the value of the leftmost subexpression (the operator) to the arguments that are the values of the other subexpressions (the operands).

    2. 将作为最左子表达式(运算符)的值的那个过程应用于相应的实际参数,所谓实际参数也就是其他子表达式(运算对象)的值。

Even this simple rule illustrates some important points about processes in general. First, observe that the first step dictates that in order to accomplish the evaluation process for a combination we must first perform the evaluation process on each element of the combination. Thus, the evaluation rule is recursive in nature; that is, it includes, as one of its steps, the need to invoke the rule itself.10

即使是一条这样简单的规则,也显示出计算过程里的一些具有普遍性的重要问题。首先,由上面的第一步可以看到,为了实现对一个组合式的求值过程,我们必须先对组合式里的每个元素执行同样的求值过程。因此,在性质上,这一求值过程是递归的,也就是说,它在自己的工作步骤中,包含着调用这个规则本身的需要10。

Notice how succinctly the idea of recursion can be used to express what, in the case of a deeply nested combination, would otherwise be viewed as a rather complicated process. For example, evaluating

在这里应该特别注意,采用递归的思想可以多么简洁地描述尝试嵌套的情况。如果不用递归,我们就需要把这种情况看成相当复杂的计算过程。例如,对下列表达式求值:

(* (+ 2 (* 4 6))

     (+ 3 5 7))

requires that the evaluation rule be applied to four different combinations. We can obtain a picture of this process by representing the combination in the form of a tree, as shown in figure 1.1. Each combination is represented by a node with branches corresponding to the operator and the operands of the combination stemming from it. The terminal nodes (that is, nodes with no branches stemming from them) represent either operators or numbers. Viewing evaluation in terms of the tree, we can imagine that the values of the operands percolate upward, starting from the terminal nodes and then combining at higher and higher levels. In general, we shall see that recursion is a very powerful technique for dealing with hierarchical, treelike objects. In fact, the ``percolate values upward'' form of the evaluation rule is an example of a general kind of process known as tree accumulation.

需要将求值规则应用于 4 个不同的组合式。如图 1.1 中所示,我们可以采用一棵树的形式,用图形表示这一组合式的求值过程,其中的每个组合式用一个带分支的结点表示,由它发出的分支对应于组合式里的运算符和各个运算对象。终端结点(即那些不再发出分支的结点)表示的是运算符或者数值。以树的观点看这种求值过程,可以设想那些运算对象的值向上穿行。从终端结点开始,而后在越来越高的层次中组合起来。一般而言,我们应该把递归看做一种处理层次性结构的(像树这样的对象)极强有力的技术。事实上,“值向上穿行”形式的求值形式是一类更一般的计算过程的一个例子,这种计算过程称为树形积累。

Figure 1.1:  Tree representation, showing the value of each subcombination.

图 1.1: 树形表示方法,其中显示了每个子表达式的值

Next, observe that the repeated application of the first step brings us to the point where we need to evaluate, not combinations, but primitive expressions such as numerals, built-in operators, or other names. We take care of the primitive cases by stipulating that

进一步的观察告诉我们,反复地应用第一个步骤,总可以把我们带到求值中的某一点,在这里遇到的不是组合式而是基本表达式,例如数、内部运算符或者其他名字。处理这些基础情况的方式如下规定:

    * the values of numerals are the numbers that they name,
    * 数的值就是它们所表示的数值。
    * the values of built-in operators are the machine instruction sequences that carry out the corresponding operations, and
    * 内部运算符的值就是能完成相应操作的机器指令序列。
    * the values of other names are the objects associated with those names in the environment.
    * 其他名字的值就是在环境中关联于这一名字的那个对象。

We may regard the second rule as a special case of the third one by stipulating that symbols such as + and * are also included in the global environment, and are associated with the sequences of machine instructions that are their ``values.'' The key point to notice is the role of the environment in determining the meaning of the symbols in expressions. In an interactive language such as Lisp, it is meaningless to speak of the value of an expression such as (+ x 1) without specifying any information about the environment that would provide a meaning for the symbol x (or even for the symbol +). As we shall see in chapter 3, the general notion of the environment as providing a context in which evaluation takes place will play an important role in our understanding of program execution.

我们可以将第二种规定看作是第三种规定的特殊情况,为此只需将像 + 和 * 一类的运算符也包含在全局环境里,并将相应的指令序列作为与之关联的“值”。对于初学者,应该指出的关键一点是,环境所扮演的角色就是用于确定表达式中各个符号的意义。在如 Lisp 这样的交互式语言里,如果没有关于有关环境的任何信息,那么说例如表达式 (+ x 1) 的值是毫无意义的,因为需要有环境为符号 x 提供意义(甚至需要它为符号 + 提供意义)。正如我们将要在第三章看到的,环境是具有普遍性的概念,它为求值过程的进行提供了一种上下文。对于我们理解程序的执行起着极其重要的作用。

Notice that the evaluation rule given above does not handle definitions. For instance, evaluating (define x 3) does not apply define to two arguments, one of which is the value of the symbol x and the other of which is 3, since the purpose of the define is precisely to associate x with a value. (That is, (define x 3) is not a combination.)

请注意,上面给出的求值规则里并没有处理定义。例如,对 (define x 3) 的求值并不是将 define 应用于它的两个实际参数:其中的一个是符号 x 的值,另一个是 3。这是因为 define 的作用就是为 x 关联一个值(也就是说,(define x 3) 并不是一个组合式)。

Such exceptions to the general evaluation rule are called special forms. Define is the only example of a special form that we have seen so far, but we will meet others shortly. Each special form has its own evaluation rule. The various kinds of expressions (each with its associated evaluation rule) constitute the syntax of the programming language. In comparison with most other programming languages, Lisp has a very simple syntax; that is, the evaluation rule for expressions can be described by a simple general rule together with specialized rules for a small number of special forms.11

一般性求值规则的这种例外称为特殊形式,define 是至今我们已经看到的惟一的一种特殊形式,下面还将看到另外一些特殊形式。每个特殊形式都有其自身的求值规则,各种不同各类的表达式(每种有着与之相关联的求值规则)组成了程序设计语言的语法形式。与大部分其他程序设计语言相比 ,Lisp 的语法非常简单。也就是说,对各种表达式的求值规则可以描述为一个简单的通用规则和一组针对不多的特殊形式的专门规则11。

1.1.4 Compound Procedures
1.1.4 复合过程

We have identified in Lisp some of the elements that must appear in any powerful programming language:

我们已经看到了 Lisp 里的某些元素,它们必然也会出现在任何一种强有力的程序设计语言里。这些东西包括:

    * Numbers and arithmetic operations are primitive data and procedures.
    * 数和算术运算是基本的数据和过程。
    * Nesting of combinations provides a means of combining operations.
    * 组合式的嵌套提供了一种组织起多个操作的方法。
    * Definitions that associate names with values provide a limited means of abstraction.
    * 定义是一种受限的抽象手段,它为名字关联相应的值。

Now we will learn about procedure definitions, a much more powerful abstraction technique by which a compound operation can be given a name and then referred to as a unit.

现在我们来学习过程定义,这是一种威力更加强大的抽象技术,通过它可以为复合操作提供名字,而后就可以将这样的操作作为一个单元使用了。

We begin by examining how to express the idea of ``squaring.'' We might say, ``To square something, multiply it by itself.'' This is expressed in our language as

现在我们要考察如何表述“平方”的想法。我们可能想说“求某个东西的平方,就是用它自身去乘以它自身”。在这个语言里,这件事情应该表述为:

(define (square x) (* x x))

We can understand this in the following way:

可以按如下方式理解这一描述:

(define (square  x)        (*         x     x))

                                      

   To      square something, multiply   it by itself.

   去      平方   某个东西   乘起       它 和 它自身

We have here a compound procedure, which has been given the name square. The procedure represents the operation of multiplying something by itself. The thing to be multiplied is given a local name, x, which plays the same role that a pronoun plays in natural language. Evaluating the definition creates this compound procedure and associates it with the name square.12

这样我们就有了一个复合过程,给它取的名字是 square。这一过程表示的是将一个东西乘以它自身的操作。被乘的东西也给定了一个局部名字 x,它扮演着与自然语言里代词同样的角色,求值这一定义的结果是创建起一个复合过程,并将它关联于名字 square12。

The general form of a procedure definition is

过程定义的一般形式是:

(define (<name> <formal parameters>) <body>)

The <name> is a symbol to be associated with the procedure definition in the environment.13 The <formal parameters> are the names used within the body of the procedure to refer to the corresponding arguments of the procedure. The <body> is an expression that will yield the value of the procedure application when the formal parameters are replaced by the actual arguments to which the procedure is applied.14 The <name> and the <formal parameters> are grouped within parentheses, just as they would be in an actual call to the procedure being defined.

其中 <name> 是一个符号,过程定义将在环境中关联于这个符号13。<formal parameters>(形式参数)是一些名字,它们用在过程体中,用于表示过程应用时与它们对应的各个实际参数。<body> 是一个表达式,在应用这一过程时,这一表达式中的形式参数将用与之对应的实际参数取代,对这样取代后的表达式的求值,产生出这个过程应用的值14。<name> 和 <formal parameters> 被放在一对括号里,成为一组,就像实际调用被定义过程时的写法。

Having defined square, we can now use it:

定义好 square 之后,我们就可以使用它了:

(square 21)

441



(square (+ 2 5))

49



(square (square 3))

81

We can also use square as a building block in defining other procedures. For example, x2 + y2 can be expressed as

我们还可以用 square 作为基本构件去定义其他过程。例如,x2 + y2 可以表述为:

(+ (square x) (square y))

We can easily define a procedure sum-of-squares that, given any two numbers as arguments, produces the sum of their squares:

现在我们很容易定义一个过程 sum-of-squares,给它两个数作为实际参数,让它产生这两个数的平方和:

(define (sum-of-squares x y)

    (+ (square x) (square y)))

 

  (sum-of-squares 3 4)

  25

Now we can use sum-of-squares as a building block in constructing further procedures:

现在我们又可以用 sum-of-squares 作为构件,进一步构造其他过程:

(define (f a)

    (sum-of-squares (+ a 1) (* a 2)))

 

  (f 5)

  136

Compound procedures are used in exactly the same way as primitive procedures. Indeed, one could not tell by looking at the definition of sum-of-squares given above whether square was built into the interpreter, like + and *, or defined as a compound procedure.

复合过程的使用方式与基本过程完全一样。实际上,如果人们只看上面 sum-of-squares 的定义,根本就无法分辨出 square 究竟是(像 + 和 * 那样)直接做在解释器里呢,还是被定义为一个复合过程。
1.1.5 The Substitution Model for Procedure Application
1.1.5 过程应用的代换模型

To evaluate a combination whose operator names a compound procedure, the interpreter follows much the same process as for combinations whose operators name primitive procedures, which we described in section 1.1.3. That is, the interpreter evaluates the elements of the combination and applies the procedure (which is the value of the operator of the combination) to the arguments (which are the values of the operands of the combination).

为了求值一个组合式(其运算符是一个复合过程的名字),解释器的工作方式将完全按照第 1.1.3 节中所描述的那样,采用与以运算符名为基本过程的组合式一样的计算过程。也就是说,解释器将对组合式的各个元素求值,而后将得到的那个过程(也就是该组合式里运算符的值)应用于那些实际参数(即组合式里那些运算对象的值)。

We can assume that the mechanism for applying primitive procedures to arguments is built into the interpreter. For compound procedures, the application process is as follows:

我们可以假定,把基本运算符应用于实参的机制已经在解释器里做好了。对于复合过程,过程应用的计算过程是:

    * To apply a compound procedure to arguments, evaluate the body of the procedure with each formal parameter replaced by the corresponding argument.
    * 将复合过程应用于实际参数,就是在将过程体中的每个形参用相应的实参取代之后,对这一过程体求值。

To illustrate this process, let's evaluate the combination

为了说明这种计算过程,让我们看看下面组合式的求值:

(f 5)

where f is the procedure defined in section 1.1.4. We begin by retrieving the body of f:

其中的 f 是第 1.1.4 节定义的那个过程。我们首先提取出 f 的体:

(sum-of-squares (+ a 1) (* a 2))

Then we replace the formal parameter a by the argument 5:

而后用实际参数 5 代换其中的形式参数 a:

(sum-of-squares (+ 5 1) (* 5 2))

Thus the problem reduces to the evaluation of a combination with two operands and an operator sum-of-squares. Evaluating this combination involves three subproblems. We must evaluate the operator to get the procedure to be applied, and we must evaluate the operands to get the arguments. Now (+ 5 1) produces 6 and (* 5 2) produces 10, so we must apply the sum-of-squares procedure to 6 and 10. These values are substituted for the formal parameters x and y in the body of sum-of-squares, reducing the expression to

这样,问题就被归为对另一个组合式的求值,其中有两个运算对象,有关的运算符是 sum-of-squares。求值这一组合式牵涉到三个子问题:我们必须对其中的运算符求值,以便得到应该去应用的那个过程;还需要求值两个运算对象,以得到过程的实际参数。这里的 (+ 5 1) 产生出 6,(* 5 2) 产生出 10,因此我们就需要将 sum-of-squares 过程用于 6 和 10。用这两个值代换 sum-of-squares 体中的形式参数 x 和 y,表达式被归约为:

(+ (square 6) (square 10))

If we use the definition of square, this reduces to

使用 square 的定义又可以将它归约为:

 (+ (* 6 6) (* 10 10))

which reduces by multiplication to

通过乘法又能将它进一步归约为:

(+ 36 100)

and finally to

最后得到:

136

The process we have just described is called the substitution model for procedure application. It can be taken as a model that determines the ``meaning'' of procedure application, insofar as the procedures in this chapter are concerned. However, there are two points that should be stressed:

上面描述的这种计算过程称为过程应用的代换模型,在考虑本章至今所定义的过程时,我们可以将它看作确定过程应用的“意义”的一种模型。但这里还需要强调两点:

    *

      The purpose of the substitution is to help us think about procedure application, not to provide a description of how the interpreter really works. Typical interpreters do not evaluate procedure applications by manipulating the text of a procedure to substitute values for the formal parameters. In practice, the ``substitution'' is accomplished by using a local environment for the formal parameters. We will discuss this more fully in chapters 3 and 4 when we examine the implementation of an interpreter in detail.

      代换的作用只是为了帮助我们领会过程调用中的情况,而不是对解释器实际工作方式的具体描述。通常的解释器都不采用直接操作过程的正文,用值去代换形式参数的方式去完成对过程调用的求值。在实际中,它们一般采用提供形式参数的局部环境的方式,产生“代换”的效果。我们将在第三章和第四章考察一个解释器的细节实现,在那里更完整地讨论这一问题。
    *

      Over the course of this book, we will present a sequence of increasingly elaborate models of how interpreters work, culminating with a complete implementation of an interpreter and compiler in chapter 5. The substitution model is only the first of these models -- a way to get started thinking formally about the evaluation process. In general, when modeling phenomena in science and engineering, we begin with simplified, incomplete models. As we examine things in greater detail, these simple models become inadequate and must be replaced by more refined models. The substitution model is no exception. In particular, when we address in chapter 3 the use of procedures with ``mutable data,'' we will see that the substitution model breaks down and must be replaced by a more complicated model of procedure application.15

      随着本书讨论的进展,我们将给出有关解释器如何工作的一系列模型,一个比一个更精细,并最终在第五章给出一个完整的解释器和一个编译器。这里的代换模型只是这些模型中的第一个——作为形式化地考虑这种求值过程的起点。一般来说,在模拟科学研究或者工程中的现象时,我们总是从最简单的不完全的模型开始。随着更细致地检查所考虑的问题,这些简单模型也会变得越来越不合适,从而必须用进一步精华的模型取代。代换模型也不例外。特别地,在第三章中,我们将要讨论将过程用于“变化的数据”的问题,那时就会看到替换模型完全不行了,必须用更复杂的过程应用模型来代替它15。

Applicative order versus normal order
应用序和正则序

According to the description of evaluation given in section 1.1.3, the interpreter first evaluates the operator and operands and then applies the resulting procedure to the resulting arguments. This is not the only way to perform evaluation. An alternative evaluation model would not evaluate the operands until their values were needed. Instead it would first substitute operand expressions for parameters until it obtained an expression involving only primitive operators, and would then perform the evaluation. If we used this method, the evaluation of

按照第第 1.1.3 节给出的有关求值的描述,解释器首先对运算符和各个运算对象求值,而后将得到的过程应用于得到的实际参数。然而,这并不是执行求值的惟一可能方式。另一种求值模型是先不求出运算对象的值,直到实际需要它们的值时再去做。采用这种求值方式,我们就应该首先用运算对象表达式去代换形式参数,直到得到一个只包含基本运算符的表达式,然后再去执行求值。如果我们采用这一方式,对下面表达式的求值:

(f 5)

would proceed according to the sequence of expansions

将按照下面的序列逐步展开:

(sum-of-squares (+ 5 1) (* 5 2))

 

  (+    (square (+ 5 1))      (square (* 5 2))  )

 

  (+    (* (+ 5 1) (+ 5 1))   (* (* 5 2) (* 5 2)))

followed by the reductions

而后是下面归约:

(+         (* 6 6)             (* 10 10))

 

  (+           36                   100)

 

                      136

This gives the same answer as our previous evaluation model, but the process is different. In particular, the evaluations of (+ 5 1) and (* 5 2) are each performed twice here, corresponding to the reduction of the expression

这给出了与前面求值模型同样的结果,但其中的计算过程却是不一样的。特别地,在对下面表达式的归约中,对于 (+ 5 1) 和 (* 5 2) 的求值各做了两次:

(* x x)

with x replaced respectively by (+ 5 1) and (* 5 2).

其中的 x 分别被代换为 (+ 5 1) 和 (* 5 2)。

This alternative ``fully expand and then reduce'' evaluation method is known as normal-order evaluation, in contrast to the ``evaluate the arguments and then apply'' method that the interpreter actually uses, which is called applicative-order evaluation. It can be shown that, for procedure applications that can be modeled using substitution (including all the procedures in the first two chapters of this book) and that yield legitimate values, normal-order and applicative-order evaluation produce the same value. (See exercise 1.5 for an instance of an ``illegitimate'' value where normal-order and applicative-order evaluation do not give the same result.)

这种“完全展开而后归约”的求值模型称为正则序求值,与之对应的是现在解释器里实际使用的“先求值参数而后应用”的方式,它称为“应用序求值。可以证明,对那些可以通过替换去模拟,并能产生出合法值过程应用(包括本书前两章中的所有过程),正则序和应用序求值将产生出同样的值(参见练习 1.5 中一个“非法”值的例子,其中正则序和应用序将给出不同的结果)。

Lisp uses applicative-order evaluation, partly because of the additional efficiency obtained from avoiding multiple evaluations of expressions such as those illustrated with (+ 5 1) and (* 5 2) above and, more significantly, because normal-order evaluation becomes much more complicated to deal with when we leave the realm of procedures that can be modeled by substitution. On the other hand, normal-order evaluation can be an extremely valuable tool, and we will investigate some of its implications in chapters 3 and 4.16

Lisp 采用应用序求值,部分原因在于这样做能避免对于表达式的重复求值(例如上面的 (+ 5 1) 和 (* 5 2) 的情况),从而可以提高一些效率。更重要的是,在超出了可以采用替换方式模拟的过程范围之后,正则序的处理将变得更复杂得多。而在另一些方面,正则序也可以成为特别有价值的工具,我们将在第三章和第四章研究它的某些内在性质16。
1.1.6 Conditional Expressions and Predicates
1.1.6 条件表达式和谓词

The expressive power of the class of procedures that we can define at this point is very limited, because we have no way to make tests and to perform different operations depending on the result of a test. For instance, we cannot define a procedure that computes the absolute value of a number by testing whether the number is positive, negative, or zero and taking different actions in the different cases according to the rule

至此我们能定义出的过程类的表达能力还非常有限,因为还没办法做某些检测,而后依据检测的结果去确定做不同的操作。例如,我们还无法定义一个过程,使它能计算出一个数的绝对值。完成此事需要先检查一个数是正的、负的或者零,而后依据遇到的不同情况,按照下面规则采取不同的动作:

This construct is called a case analysis, and there is a special form in Lisp for notating such a case analysis. It is called cond (which stands for ``conditional''), and it is used as follows:

这种结构称为一个分情况分析,在 Lisp 里有着一种针对这类分情况分析的特殊形式,称为 cond(表示“条件”)。其使用形式如下:

(define (abs x)

    (cond ((> x 0) x)

          ((= x 0) 0)

          ((< x 0) (- x))))

The general form of a conditional expression is

条件表达式的一般性形式为:

(cond (<p1> <e1>)

        (<p2> <e2>)

       

        (<pn> <en>))

consisting of the symbol cond followed by parenthesized pairs of expressions (<p> <e>) called clauses. The first expression in each pair is a predicate -- that is, an expression whose value is interpreted as either true or false.17

这是首先包含了一个符号 cond,在它之后跟着一些称为子句的用括号括起的表达式对偶(<p> <e>)。在每个对偶中的第一个表达式是一个谓词,也就是说,这是一个表达式,它的值将被解释为真或者假17。

Conditional expressions are evaluated as follows. The predicate <p1> is evaluated first. If its value is false, then <p2> is evaluated. If <p2>'s value is also false, then <p3> is evaluated. This process continues until a predicate is found whose value is true, in which case the interpreter returns the value of the corresponding consequent expression <e> of the clause as the value of the conditional expression. If none of the <p>'s is found to be true, the value of the cond is undefined.

条件表达式的求值方式如下:首先求值谓词 <p1>,如果它的值是 false,那么就去求值 <p2>,如果 <p2> 的值是 false 就去求值 <p3>。这一过程将继续做下去,直到发现了某个谓词的值为真为止。此时解释器就返回相应子句的序列表达式 <e> 的值作为整个条件表达式的值。如果无法找到值为真的 <p>,cond 的值就没有定义。

The word predicate is used for procedures that return true or false, as well as for expressions that evaluate to true or false. The absolute-value procedure abs makes use of the primitive predicates >, <, and =.18 These take two numbers as arguments and test whether the first number is, respectively, greater than, less than, or equal to the second number, returning true or false accordingly.

我们用术语谓词指那些返回真或假的过程,也指那种能求出真或者假的值的表达式。求绝对值的过程 abs 使用了基本谓词 >、< 和 =18,这几个谓词都以两个数为参数,分别检查第一个数是否大于、小于或者等于第二个数,并据此分别返回真或者假。

Another way to write the absolute-value procedure is

写绝对值函数的另一种方式是:

(define (abs x)

    (cond ((< x 0) (- x))

          (else x)))

which could be expressed in English as ``If x is less than zero return - x; otherwise return x.'' Else is a special symbol that can be used in place of the <p> in the final clause of a cond. This causes the cond to return as its value the value of the corresponding <e> whenever all previous clauses have been bypassed. In fact, any expression that always evaluates to a true value could be used as the <p> here.

用自然语言来说,就是“如果 x 小于 0 就返回 -x,否则就返回 x”。else 是一个特殊符号,可以用在 cond 的最后一个子句中 <p> 的位置,这样做时,如果该 cond 前面的所有子句都被跳过,它就会返回最后子句中 <e> 的值。事实上,所有永远都求出真值的表达式都可以用在这个 <p> 的位置上。

Here is yet another way to write the absolute-value procedure:

下面是又一种写绝对值函数的方式:

(define (abs x)

    (if (< x 0)

        (- x)

        x))

This uses the special form if, a restricted type of conditional that can be used when there are precisely two cases in the case analysis. The general form of an if expression is

这里采用的是特殊形式 if,它是条件表达式的一种受限形式,适用于分情况分析中只有两个情况的需要,if 表达式的一般形式是:

(if <predicate> <consequent> <alternative>)

To evaluate an if expression, the interpreter starts by evaluating the <predicate> part of the expression. If the <predicate> evaluates to a true value, the interpreter then evaluates the <consequent> and returns its value. Otherwise it evaluates the <alternative> and returns its value.19

在求值一个 if 表达式时,解释器从求值其 <predicate> 部分开始,如果 <predicate> 得到真值,解释器就去求值 <consequent> 并返回其值,否则它就去求值 <alternative>19。

In addition to primitive predicates such as <, =, and >, there are logical composition operations, which enable us to construct compound predicates. The three most frequently used are these:

除了一批基本谓词如 <、= 和 > 之外,还有一些逻辑复合运算符,复用它们可以构造出各种复合谓词。最常用的三个复合运算符是:

    *
    * (and <e1> ... <en>)

      The interpreter evaluates the expressions <e> one at a time, in left-to-right order. If any <e> evaluates to false, the value of the and expression is false, and the rest of the <e>'s are not evaluated. If all <e>'s evaluate to true values, the value of the and expression is the value of the last one.

      解释器将从左到右一个个地求值 <e>,如果某个 <e> 求值得到假,这一 and 表达式的值就是假,后面的那些 <e> 也不再求值了。如果前面所有的 <e> 都求出真值,这一 and 表达式的值就是最后那个 <e> 的值。
    * (or <e1> ... <en>)

      The interpreter evaluates the expressions <e> one at a time, in left-to-right order. If any <e> evaluates to a true value, that value is returned as the value of the or expression, and the rest of the <e>'s are not evaluated. If all <e>'s evaluate to false, the value of the or expression is false.

      解释器将从左到右一个个地求值 <e>,如果某个 <e> 求值得到真,<or> 表达式就以这个表达式的值作为值,后面的那些 <e> 也不再求值了。如果所有的 <e> 都求出假值,这一 or 表达式的值就是假。
    * (not <e>)

      The value of a not expression is true when the expression <e> evaluates to false, and false otherwise.

      如果 <e> 求出的值是假,not 表达式的值就是真;否则其值为假。

Notice that and and or are special forms, not procedures, because the subexpressions are not necessarily all evaluated. Not is an ordinary procedure.

注意,and 和 or 都是特殊形式而不是普通的过程,因为它们的子表达式不一定都求值。not 则是一个普通的过程。

As an example of how these are used, the condition that a number x be in the range 5 < x < 10 may be expressed as

作为使用这些逻辑复合运算符的例子,数 x 的值位于区间 5 < x < 10 之中的条件可以写为:

(and (> x 5) (< x 10))

As another example, we can define a predicate to test whether one number is greater than or equal to another as

作为另一个例子,下面定义了一个谓词,它检测某个数是否大于或者等于另一个数:

(define (>= x y)

    (or (> x y) (= x y)))

or alternatively as

或者也可以定义为:

(define (>= x y)

    (not (< x y)))

Exercise 1.1.  Below is a sequence of expressions. What is the result printed by the interpreter in response to each expression? Assume that the sequence is to be evaluated in the order in which it is presented.

练习 1.1. 下面是一系列表达式,对于每个表达式,解释器将输出什么结果?假定这一系列表达式是按照给出的顺序逐个求值的。

10

  (+ 5 3 4)

  (- 9 1)

  (/ 6 2)

  (+ (* 2 4) (- 4 6))

  (define a 3)

  (define b (+ a 1))

  (+ a b (* a b))

  (= a b)

  (if (and (> b a) (< b (* a b)))

      b

      a)

  (cond ((= a 4) 6)

        ((= b 4) (+ 6 7 a))

        (else 25))

  (+ 2 (if (> b a) b a))

  (* (cond ((> a b) a)

           ((< a b) b)

           (else -1))

     (+ a 1))

Exercise 1.2.  Translate the following expression into prefix form

练习 1.2. 请将下面表达式变换为前缀形式:

Exercise 1.3. Define a procedure that takes three numbers as arguments and returns the sum of the squares of the two larger numbers.

练习 1.3. 请定义一个过程,它以三个数为参数,返回其中较大的两个数之和。

Exercise 1.4. Observe that our model of evaluation allows for combinations whose operators are compound expressions. Use this observation to describe the behavior of the following procedure:

练习 1.4. 请仔细考察上面给出的允许运算符为复合表达式的组合式的求值模型,根据对这一模型的认识描述下面过程的行为:

(define (a-plus-abs-b a b)

    ((if (> b 0) + -) a b))


Exercise 1.5. Ben Bitdiddle has invented a test to determine whether the interpreter he is faced with is using applicative-order evaluation or normal-order evaluation. He defines the following two procedures:

练习 1.5. Ben Bitdiddle 发明了一种检测方法,能够确定解释器究竟采用哪种序求值,是采用应用序,还是采用正则序。他定义了下面两个过程:

(define (p) (p))

 

  (define (test x y)

    (if (= x 0)

        0

        y))

Then he evaluates the expression

而后他求值下面的表达式:

(test 0 (p))

What behavior will Ben observe with an interpreter that uses applicative-order evaluation? What behavior will he observe with an interpreter that uses normal-order evaluation? Explain your answer. (Assume that the evaluation rule for the special form if is the same whether the interpreter is using normal or applicative order: The predicate expression is evaluated first, and the result determines whether to evaluate the consequent or the alternative expression.)

如果某个解释器采用的是应用序求值,Ben 会看到什么样的情况?如果解释器采用正则序求值,他又会看到什么情况?请对你的回答做出解释。(无论采用正则序或者应用序,假定特殊形式 if 的求值规则总是一样的。其中的谓词部分先行求值,根据其结果确定随后求值的子表达式部分。)

1.1.7 Example: Square Roots by Newton's Method
1.1.7 实例:采用牛顿法求平方根

Procedures, as introduced above, are much like ordinary mathematical functions. They specify a value that is determined by one or more parameters. But there is an important difference between mathematical functions and computer procedures. Procedures must be effective.

上面介绍的过程都很像常规的数学函数,它们描述的是如何根据一个或者几个参数去确定一个值。然而,在数学的函数和计算机的过程之间有一个重要差异,那就是,这一过程还必须是有效可行的。

As a case in point, consider the problem of computing square roots. We can define the square-root function as

作为目前情况下的一个实例,现在我们来考虑求平方根的问题。我们可以将平方根函数定义为:

This describes a perfectly legitimate mathematical function. We could use it to recognize whether one number is the square root of another, or to derive facts about square roots in general. On the other hand, the definition does not describe a procedure. Indeed, it tells us almost nothing about how to actually find the square root of a given number. It will not help matters to rephrase this definition in pseudo-Lisp:

这就描述出了一个完全正统的数学函数,我们可以利用它去判断某个数是否为另一个数的平方根,或根据上面叙述。推导出一些有关平方根的一般性事实。然而,在另一方面,这一定义并没有描述一个计算过程,因为它确实没有告诉我们,在给定了一个数之后,如何实际地找到这个数的平方根。即使将这个定义用类似 Lisp 形式重写一遍也完全无济于事。

(define (sqrt x)

    (the y (and (>= y 0)

                (= (square y) x))))

This only begs the question.

这只不过是重新提出了原来的问题。

The contrast between function and procedure is a reflection of the general distinction between describing properties of things and describing how to do things, or, as it is sometimes referred to, the distinction between declarative knowledge and imperative knowledge. In mathematics we are usually concerned with declarative (what is) descriptions, whereas in computer science we are usually concerned with imperative (how to) descriptions.20

函数与过程之间的矛盾,不过是在描述一件事情的特征,与描述如何去做这件事情之间的普遍性差异的一个具体反映。换一种说法,人们有时也将它说成是说明性的知识与行动性的知识之间的差异。在数学里,人们通常关心的是说明性的描述(是什么),而在计算机科学里,人们则通常关心行动性的描述(怎么做)20。

How does one compute square roots? The most common way is to use Newton's method of successive approximations, which says that whenever we have a guess y for the value of the square root of a number x, we can perform a simple manipulation to get a better guess (one closer to the actual square root) by averaging y with x/y.21 For example, we can compute the square root of 2 as follows. Suppose our initial guess is 1:

计算机如何算出平方根呢?最常用的就是牛顿的逐步逼进方法。这一方法告诉我们,如果对 x 的平方根的值有一个猜测 y,那么就可以通过执行一个简单操作去得到一个更好的猜测:只需要求出 y 和 x/y 的平均值(它更接近实际的平方根值)21。例如,可以用这种方式去计算 2 的平方根,假定初始值是 1:
Guess     Quotient     Average
 
1     (2/1) = 2     ((2 + 1)/2) = 1.5
 
1.5     (2/1.5) = 1.3333     ((1.3333 + 1.5)/2) = 1.4167
 
1.4167     (2/1.4167) = 1.4118     ((1.4167 + 1.4118)/2) = 1.4142
 
1.4142     ...     ...

Continuing this process, we obtain better and better approximations to the square root.

继续这一计算过程,我们就能得到对 2 的平方根的越来越好的近似值。

Now let's formalize the process in terms of procedures. We start with a value for the radicand (the number whose square root we are trying to compute) and a value for the guess. If the guess is good enough for our purposes, we are done; if not, we must repeat the process with an improved guess. We write this basic strategy as a procedure:

现在,让我们设法用过程的语言来描述这一计算过程。开始时,我们有了被开方数的值(现在需要做的就是算出它的平方根)和一个猜测值。如果猜测值已经足够好了,有关工作也就完成了。如若不然,那么就需要重复上述计算过程去改进猜测值。我们可以将这一基本策略写成下面的过程:

(define (sqrt-iter guess x)

    (if (good-enough? guess x)

        guess

        (sqrt-iter (improve guess x)

                   x)))

A guess is improved by averaging it with the quotient of the radicand and the old guess:

改进猜测的方式就是求出它与被开方数除以上一个猜测的平均值:

(define (improve guess x)

    (average guess (/ x guess)))

where

其中

 

(define (average x y)
  (/ (+ x y) 2))

We also have to say what we mean by ``good enough.'' The following will do for illustration, but it is not really a very good test. (See exercise 1.7.) The idea is to improve the answer until it is close enough so that its square differs from the radicand by less than a predetermined tolerance (here 0.001):22

我们还必须说明什么叫做“足够好”。下面的做法只是为了说明问题,它确实不是一个很好的检测方法(参见练习 1.7)。这里的想法是,不断改进答案直到它足够接近平方根,使得其平方与被开方数之差小于某个事先确定的误差值(这里用的是 0.001)22:

(define (good-enough? guess x)

    (< (abs (- (square guess) x)) 0.001))

Finally, we need a way to get started. For instance, we can always guess that the square root of any number is 1:23

最后还需要一种方式来启动整个工作。例如,我们可以总用 1 作为对任何数的初始猜测值23:

(define (sqrt x)

    (sqrt-iter 1.0 x))

If we type these definitions to the interpreter, we can use sqrt just as we can use any procedure:

如果把这些定义都送给解释器,我们就可以使用 sqrt 了,就像可以使用其他过程一样:

(sqrt 9)

  3.00009155413138

  (sqrt (+ 100 37))

  11.704699917758145

  (sqrt (+ (sqrt 2) (sqrt 3)))

  1.7739279023207892

  (square (sqrt 1000))

  1000.000369924366

The sqrt program also illustrates that the simple procedural language we have introduced so far is sufficient for writing any purely numerical program that one could write in, say, C or Pascal. This might seem surprising, since we have not included in our language any iterative (looping) constructs that direct the computer to do something over and over again. Sqrt-iter, on the other hand, demonstrates how iteration can be accomplished using no special construct other than the ordinary ability to call a procedure.24

这个 sqrt 程序也说明,在用于写纯粹的数值计算程序时,至今已介绍的简单程序设计语言已经足以写出在其他语言(例如 C 或者 Pascal)中写出的任何东西了。这看起来很人吃惊,因为这一语言中甚至还没有包括任何迭代结构(循环),它们用于指针计算机去一遍遍地做某些事情。而在另一方面,sqrt-iter 展示了如何不用特殊的迭代结构来实现迭代,其中只需要使用常规的过程调用能力24。

Exercise 1.6.  Alyssa P. Hacker doesn't see why if needs to be provided as a special form. ``Why can't I just define it as an ordinary procedure in terms of cond?'' she asks. Alyssa's friend Eva Lu Ator claims this can indeed be done, and she defines a new version of if:

练习 1.6. Alyssa P.Hacker 看不出为什么需要将 if 提供为一种特殊形式,她问:“为什么我不能直接通过 cond 将它定义为一个常规过程呢?”Alyssa 的朋友 Eva lU Ator 断言确实可以这样做,并定义了 if 的一个新版本:

(define (new-if predicate then-clause else-clause)

    (cond (predicate then-clause)

          (else else-clause)))

Eva demonstrates the program for Alyssa:

Eva 给 Alyssa 演示她的程序:

(new-if (= 2 3) 0 5)

  5

 

  (new-if (= 1 1) 0 5)

  0

Delighted, Alyssa uses new-if to rewrite the square-root program:

她很高兴地用自己的 new-if 重定了求平方根的程序:

(define (sqrt-iter guess x)

    (new-if (good-enough? guess x)

            guess

            (sqrt-iter (improve guess x)

                       x)))

What happens when Alyssa attempts to use this to compute square roots? Explain.

当 Alyssa 试着用这个过程去计算平方根时会发生什么事情呢?请给出解释。

Exercise 1.7.  The good-enough? test used in computing square roots will not be very effective for finding the square roots of very small numbers. Also, in real computers, arithmetic operations are almost always performed with limited precision. This makes our test inadequate for very large numbers. Explain these statements, with examples showing how the test fails for small and large numbers. An alternative strategy for implementing good-enough? is to watch how guess changes from one iteration to the next and to stop when the change is a very small fraction of the guess. Design a square-root procedure that uses this kind of end test. Does this work better for small and large numbers?

练习 1.7. 对于确定很小的数的平方根而言,在计算平方根中使用的检测 good-enough? 是很不好的。还有,在现实的计算机里,算术运算总是以一定的有限精度进行的。这也会使我们的检测不适合非常大的数的计算。请解释上述论断,用例子说明对很小和很大的数,这种检测都可能失败。实现 good-enough? 的另一种策略是监视猜测值在从一次迭代到下一次的变化情况,当改变值对于猜测值的比率很小时就结束。请设计一个采用这种终止测试方式的平方根过程。对于很大和很小的数,这一方式都能工作吗?guess

Exercise 1.8.  Newton's method for cube roots is based on the fact that if y is an approximation to the cube root of x, then a better approximation is given by the value

练习 1.8. 求立方根的牛顿法基于如下事实,如果 y 是 x 的立方根的一个近似值,那么下式将给出一个更好的近似值:

Use this formula to implement a cube-root procedure analogous to the square-root procedure. (In section 1.3.4 we will see how to implement Newton's method in general as an abstraction of these square-root and cube-root procedures.)

请利用这一公式实现一个类似平方根过程的求立方根的过程。(第 1.3.4 节里,我们将看到如何实现一般性的牛顿法,作为这些求平方根和立方根过程的抽象。)
1.1.8 Procedures as Black-Box Abstractions
1.1.8 过程作为黑箱抽象

Sqrt is our first example of a process defined by a set of mutually defined procedures. Notice that the definition of sqrt-iter is recursive; that is, the procedure is defined in terms of itself. The idea of being able to define a procedure in terms of itself may be disturbing; it may seem unclear how such a ``circular'' definition could make sense at all, much less specify a well-defined process to be carried out by a computer. This will be addressed more carefully in section 1.2. But first let's consider some other important points illustrated by the sqrt example.

sqrt 是我们用一组手工定义的过程来实现一个计算过程的第一例子。请注意,在这里 sqrt-iter 的定义是递归的,也就是说,这一过程的定义基于它自身。能够基于一个过程自身来定义它的想法很可能会令人感到不安,人们可能觉得它不够清晰,这种“循环”定义怎么能有意义呢?是不是完全刻画了一个能够由计算机实现的计算过程呢?在第 1.2 节里,我们将更细致地讨论这一问题。现在首先来看看 sqrt 实例所显示出的其他一些要点。

Observe that the problem of computing square roots breaks up naturally into a number of subproblems: how to tell whether a guess is good enough, how to improve a guess, and so on. Each of these tasks is accomplished by a separate procedure. The entire sqrt program can be viewed as a cluster of procedures (shown in figure 1.2) that mirrors the decomposition of the problem into subproblems.

可以看到,对于平方根的计算问题可以自然地分解为若干子问题:怎样说一个猜测是足够好了,怎样去改进一个猜测,等等。这些工作中的每一个都通过一个独立的过程完成,整个 sqrt 程序可以看做一族过程(如图 1.2 所示),它们直接反应了从原问题到子问题的分解。

Figure 1.2:  Procedural decomposition of the sqrt program.

图 1.2: sqrt 程序的过程分解

The importance of this decomposition strategy is not simply that one is dividing the program into parts. After all, we could take any large program and divide it into parts -- the first ten lines, the next ten lines, the next ten lines, and so on. Rather, it is crucial that each procedure accomplishes an identifiable task that can be used as a module in defining other procedures. For example, when we define the good-enough? procedure in terms of square, we are able to regard the square procedure as a ``black box.'' We are not at that moment concerned with how the procedure computes its result, only with the fact that it computes the square. The details of how the square is computed can be suppressed, to be considered at a later time. Indeed, as far as the good-enough? procedure is concerned, square is not quite a procedure but rather an abstraction of a procedure, a so-called procedural abstraction. At this level of abstraction, any procedure that computes the square is equally good.

这一分解的重要性,并不仅仅在于它将一个问题分解成了几个部分。当然,我们总可以拿来一个大程序,并将它侵害成若干部分——最前面 10 行、后面 10 行、再后面 10 行等等。这里最关键的问题是,分解中的每一个过程完成了一件可以清楚标明的工作,这使它们可以被用作定义其他过程的模块。例如,当我们基于 square 定义过程 good-enough? 之时,就是将 square 看做一个“黑箱”。在这样做时,我们根本无须关注这个过程是如何计算出它的结果的,只需要注意它能计算出平方值的事实。关于平方是如何计算的细节被隐去不提了,可以推迟到后来再考虑。情况确实如此,如果只看 good-enough? 过程,与其说 square 是一个过程,不如说它是一个过程的抽象,即所谓的过程抽象。在这一抽象层次上,任何能计算出平方的过程都同样可以用。

Thus, considering only the values they return, the following two procedures for squaring a number should be indistinguishable. Each takes a numerical argument and produces the square of that number as the value.25

这样,我们只考虑返回值,那么下面这两个求平方的过程就是不可区分的。它们中的每一个都取一个数值参数,产生出这个数的平方作为值25。
(define (square x) (* x x))

(define (square x)
  (exp (double (log x))))

(define (double x) (+ x x))

So a procedure definition should be able to suppress detail. The users of the procedure may not have written the procedure themselves, but may have obtained it from another programmer as a black box. A user should not need to know how the procedure is implemented in order to use it.

由此可见,一个过程定义应该能隐藏起一些细节。这将使过程的使用者可能不必自己去写这些过程,而是从其他程序员那里作为一个黑箱而接受了它。用户在使用一个过程时,应该不需要去弄清它是如何实现的。
Local names
局部名

One detail of a procedure's implementation that should not matter to the user of the procedure is the implementer's choice of names for the procedure's formal parameters. Thus, the following procedures should not be distinguishable:

过程用户不必去关心的实现细节之一,就是在有关的过程里面形式参数的名字,这是由实现者所选用的。也就是说,下面两个过程定义应该是无法区分的:
(define (square x) (* x x))

(define (square y) (* y y))

This principle -- that the meaning of a procedure should be independent of the parameter names used by its author -- seems on the surface to be self-evident, but its consequences are profound. The simplest consequence is that the parameter names of a procedure must be local to the body of the procedure. For example, we used square in the definition of good-enough? in our square-root procedure:

这一原则(过程的意义应该不依赖于其作者为形式参数所选用的名字)从表面看起来很明显,但其影响却非常深远。最直接的影响是,过程的形式参数名必须局部于有关的过程体。例如,我们在前面平方根程序中的 good-enough? 定义里使用了 square:
(define (good-enough? guess x)
  (< (abs (- (square guess) x)) 0.001))

The intention of the author of good-enough? is to determine if the square of the first argument is within a given tolerance of the second argument. We see that the author of good-enough? used the name guess to refer to the first argument and x to refer to the second argument. The argument of square is guess. If the author of square used x (as above) to refer to that argument, we see that the x in good-enough? must be a different x than the one in square. Running the procedure square must not affect the value of x that is used by good-enough?, because that value of x may be needed by good-enough? after square is done computing.

good-enough? 作者的意图就是要去确定,函数的第一个参数的平方是否位于第二个参数附近一定的误差范围内。可以看到,good-enough? 的作者用名字 guess 表示其第一个参数,用 x 表示第二个参数,而送给 square 的实际参数就是 guess。如果 square 的作者也用 x(上面确实如此)表示参数,那么就可以明显看出,good-enough? 里的 x 必须与 square 里的那个 x 不同。在过程 square 运行时,绝不应该影响 good-enough? 里所用的那个 x 的值,因为在 square 完成计算之后,good-enough? 里可能还需要用 x 的值。

If the parameters were not local to the bodies of their respective procedures, then the parameter x in square could be confused with the parameter x in good-enough?, and the behavior of good-enough? would depend upon which version of square we used. Thus, square would not be the black box we desired.

如果参数不是它们所在的过程体里局部的东西,那么 square 里的 x 会与 good-enough? 里的参数 x 相混淆。如果这样,good-enough? 的行为方式就将依赖于我们所用的 square 的不同版本。这样,square 也就不是我们所希望的黑箱了。

A formal parameter of a procedure has a very special role in the procedure definition, in that it doesn't matter what name the formal parameter has. Such a name is called a bound variable, and we say that the procedure definition binds its formal parameters. The meaning of a procedure definition is unchanged if a bound variable is consistently renamed throughout the definition.26 If a variable is not bound, we say that it is free. The set of expressions for which a binding defines a name is called the scope of that name. In a procedure definition, the bound variables declared as the formal parameters of the procedure have the body of the procedure as their scope.

过程的形式参数在过程体里扮演着一种非常特殊的角色,在这里,形式参数的具体名字是什么,其实完全没有关系。这样的名字称为约束变量,因此我们说,一个过程的定义约束了它的所有形式参数。如果在一个完整的过程定义里将某个约束变量统一换名,这一过程定义的意义将不会有任何改变26。如果一个变量不是被约束的,我们就称它为自由的。一个名字的定义被约束于的那一集表达式称为这个名字的作用域。在一个过程定义里,被声明为这个过程的形式参数的那些约束变量,就以这个过程的体作为它们的作用域。

In the definition of good-enough? above, guess and x are bound variables but <, -, abs, and square are free. The meaning of good-enough? should be independent of the names we choose for guess and x so long as they are distinct and different from <, -, abs, and square. (If we renamed guess to abs we would have introduced a bug by capturing the variable abs. It would have changed from free to bound.) The meaning of good-enough? is not independent of the names of its free variables, however. It surely depends upon the fact (external to this definition) that the symbol abs names a procedure for computing the absolute value of a number. Good-enough? will compute a different function if we substitute cos for abs in its definition.

在上面 good-enough? 的定义中,guess 和 x 是约束变量,而 <、-、abs 和 square 则是自由的。要想保证 good-enough? 的意义与我们对 guess 和 x 的名字选择无关,只要求它们的名字与 <、-、abs 和 square 都不同就可以了(如果将 guess 重新命名为 abs,我们就会因为捕获了变量名 abs 而引进了一个错误,因为这样做就把一个原本自由的名字变成约束的了)。good-enough? 的意义当然与其中的自由变量有关,显然它的意义依赖于(在这一定义之外的)一些事实:要求符号 abs 是一个过程的名字,该过程能求出一个数的绝对值。如果我们将 good-enough? 的定义里的 abs 换成 cos,它计算出的就会是另一个不同函数了。
Internal definitions and block structure
内部定义和块结构

We have one kind of name isolation available to us so far: The formal parameters of a procedure are local to the body of the procedure. The square-root program illustrates another way in which we would like to control the use of names. The existing program consists of separate procedures:

至今我们才仅仅分离出了一种可用的名字:过程的形式参数是相应过程体里的局部名字。平方根程序还展现出了另一种情况,我们也会希望能控制其中的名字使用。现在这个程序由几个相互分离的过程组成:
(define (sqrt x)
  (sqrt-iter 1.0 x))
(define (sqrt-iter guess x)
  (if (good-enough? guess x)
      guess
      (sqrt-iter (improve guess x) x)))
(define (good-enough? guess x)
  (< (abs (- (square guess) x)) 0.001))
(define (improve guess x)
  (average guess (/ x guess)))

The problem with this program is that the only procedure that is important to users of sqrt is sqrt. The other procedures (sqrt-iter, good-enough?, and improve) only clutter up their minds. They may not define any other procedure called good-enough? as part of another program to work together with the square-root program, because sqrt needs it. The problem is especially severe in the construction of large systems by many separate programmers. For example, in the construction of a large library of numerical procedures, many numerical functions are computed as successive approximations and thus might have procedures named good-enough? and improve as auxiliary procedures. We would like to localize the subprocedures, hiding them inside sqrt so that sqrt could coexist with other successive approximations, each having its own private good-enough? procedure. To make this possible, we allow a procedure to have internal definitions that are local to that procedure. For example, in the square-root problem we can write

问题是,在这个程序里只有一个过程对用户是重要的,那就是,这里所定义的这个 sqrt 确实是 sqrt。其他的过程 (sqrt-iter、good-enough? 和 improve) 则只会干扰他们的思维,因为他们再也不能定义另一个称为 good-enough? 的过程,作为需要与平方根程序一起使用的其他程序的一部分了,因为现在 sqrt 需要它。在许多程序员一起构造大系统的时候,这一问题将会变得非常严重。举例来说,在构造一个大型的数值过程库时,许多数值函数都需要计算出一系列的近似值,因此我们就可能希望有一些名字为 good-enough? 和 improve 的过程作为其中的辅助过程。由于这些情况,我们也希望将这种子过程局部化,将它们隐藏到 sqrt 里面,以使 sqrt 可以与其他采用逐步逼进的过程共存,让它们中的每一个都有自己的 good-enough? 过程。为了使这一方式成为可能,我们要允许一个过程里带有一些内部定义,使它们是局部于这一过程的。例如,在解决平方根问题,我们可以写:
(define (sqrt x)
  (define (good-enough? guess x)
    (< (abs (- (square guess) x)) 0.001))
  (define (improve guess x)
    (average guess (/ x guess)))
  (define (sqrt-iter guess x)
    (if (good-enough? guess x)
        guess
        (sqrt-iter (improve guess x) x)))
  (sqrt-iter 1.0 x))

Such nesting of definitions, called block structure, is basically the right solution to the simplest name-packaging problem. But there is a better idea lurking here. In addition to internalizing the definitions of the auxiliary procedures, we can simplify them. Since x is bound in the definition of sqrt, the procedures good-enough?, improve, and sqrt-iter, which are defined internally to sqrt, are in the scope of x. Thus, it is not necessary to pass x explicitly to each of these procedures. Instead, we allow x to be a free variable in the internal definitions, as shown below. Then x gets its value from the argument with which the enclosing procedure sqrt is called. This discipline is called lexical scoping.27

这种嵌套的定义称为块结构,它是简单的名字包装问题的一种正确解决方式。实际上,在这里还潜藏着一个很好的想法。除了可以将所用的辅助过程定义放到内部,我们还可能简单化它们。因为 x 在 sqrt 的定义中是受约束的,过程 good-enough?、improve 和 sqrt-iter 也都定义在 sqrt 里面,也就是说,都在 x 定义域里。这样,显式地将 x 在这些过程之间传来传去就没有必要了。我们可以让 x 作为内部定义中的自由变量,如下所示。这样,在外围的 sqrt 被调用时,x 由实际参数得到自己的值。这种方式称为词法作用域27。
(define (sqrt x)
  (define (good-enough? guess)
    (< (abs (- (square guess) x)) 0.001))
  (define (improve guess)
    (average guess (/ x guess)))
  (define (sqrt-iter guess)
    (if (good-enough? guess)
        guess
        (sqrt-iter (improve guess))))
  (sqrt-iter 1.0))

We will use block structure extensively to help us break up large programs into tractable pieces.28 The idea of block structure originated with the programming language Algol 60. It appears in most advanced programming languages and is an important tool for helping to organize the construction of large programs.

下面将广泛使用这种块结构,以帮助我们将大程序分解成一些容易把握的片段28。块结构的思想来自程序设计语言 Algol 60,这种结构出现在各种最新的程序设计语言里,是帮助我们组织大程序的结构的一种重要工具。

4 The characterization of numbers as ``simple data'' is a barefaced bluff. In fact, the treatment of numbers is one of the trickiest and most confusing aspects of any programming language. Some typical issues involved are these: Some computer systems distinguish integers, such as 2, from real numbers, such as 2.71. Is the real number 2.00 different from the integer 2? Are the arithmetic operations used for integers the same as the operations used for real numbers? Does 6 divided by 2 produce 3, or 3.0? How large a number can we represent? How many decimal places of accuracy can we represent? Is the range of integers the same as the range of real numbers? Above and beyond these questions, of course, lies a collection of issues concerning roundoff and truncation errors -- the entire science of numerical analysis. Since our focus in this book is on large-scale program design rather than on numerical techniques, we are going to ignore these problems. The numerical examples in this chapter will exhibit the usual roundoff behavior that one observes when using arithmetic operations that preserve a limited number of decimal places of accuracy in noninteger operations.

4将数值作为“简单数据”看待实际上完全是一种虚张声势。事实上,对于数值的处理是任何程序设计语言里最错综复杂而且也最迷惑人的事项之一,其中涉及的典型问题包括:某些计算机系统区分了整数(例如 2)和实数(例如 2.71)。那么实数 2.00 和整数 2 不同吗?用于整数的算术运算是否与用于实数的运算相同呢?用 6 以 2 的结果是 3 还是 3.0?我们可以表示的最大的数是多少?最多能表示的精度包含了多少个十进制位?整数的表示范围与实数一样吗?显然,上述这些问题以及许多其他问题。都会带来有关舍入和截断误差的一系列问题——这就是数值分析的整个科学领域。因为我们在本书中主要关心的是大规模程序的设计,而不是数值技术,因此将忽略对这些问题的讨论。本章中有关数值的实例没有常规的舍入动作,而如果对非整数使用具有有限的二进制位数精度的算术运算,就会看到这方面的情况。

5 Throughout this book, when we wish to emphasize the distinction between the input typed by the user and the response printed by the interpreter, we will show the latter in slanted characters.

5在这本书里的任何地方,当我们希望强调用户键入的输入和解释器的响应之间的差异时,就用斜体的形式显示后者。

6 Lisp systems typically provide features to aid the user in formatting expressions. Two especially useful features are one that automatically indents to the proper pretty-print position whenever a new line is started and one that highlights the matching left parenthesis whenever a right parenthesis is typed.

6Lisp 系统通常都为用户提供了一些对表达式进行格式化的特征。其中包含两个最有用的特征,其一是在开始一个新行时,自动缩格到美观打印形式的准确位置;另一特征是在输入右括号时自动加亮显示与之对应的左括号。

7 Lisp obeys the convention that every expression has a value. This convention, together with the old reputation of Lisp as an inefficient language, is the source of the quip by Alan Perlis (paraphrasing Oscar Wilde) that ``Lisp programmers know the value of everything but the cost of nothing.''

7Lisp 遵循一种约定,规定每个表达式都有一个值。这一约定和有关 Lisp 是一个低效语言的陈旧说法一起,形成了 Alan Perlis 妙语(由 Oscar Wilde 释义):“Lisp 程序员知道所有东西的值(value,价值),但却不知道任何东西的代价(cost)。”

8 In this book, we do not show the interpreter's response to evaluating definitions, since this is highly implementation-dependent.

8本书中将不给出解释器在对定义求值时的响应,因为这依赖于具体实现。

9 Chapter 3 will show that this notion of environment is crucial, both for understanding how the interpreter works and for implementing interpreters.

9第三章将说明,无论对于理解解释器的工作,还是实现解释器而言,环境的概念都是至关重要的。

10 It may seem strange that the evaluation rule says, as part of the first step, that we should evaluate the leftmost element of a combination, since at this point that can only be an operator such as + or * representing a built-in primitive procedure such as addition or multiplication. We will see later that it is useful to be able to work with combinations whose operators are themselves compound expressions.

10这一求值规则说,在它的第一步要对组合式的最左元素求值,这一说法看起来好像有点奇怪,因为在这里出现的只是 + 和 * 一类的运算符,它们表示的是内部基本过程,例如求和和求乘积。后面将看到这一规则是有用的,因为我们还需要处理那些运算符部分也是组合表达式的情况。

11 Special syntactic forms that are simply convenient alternative surface structures for things that can be written in more uniform ways are sometimes called syntactic sugar, to use a phrase coined by Peter Landin. In comparison with users of other languages, Lisp programmers, as a rule, are less concerned with matters of syntax. (By contrast, examine any Pascal manual and notice how much of it is devoted to descriptions of syntax.) This disdain for syntax is due partly to the flexibility of Lisp, which makes it easy to change surface syntax, and partly to the observation that many ``convenient'' syntactic constructs, which make the language less uniform, end up causing more trouble than they are worth when programs become large and complex. In the words of Alan Perlis, ``Syntactic sugar causes cancer of the semicolon.''

11这里的特殊语法形式,只不过是为那些完全可以采用统一形式描述的东西给出的另一种表面结构,通常被称为语法糖衣,这个术语源自 Peter Landin。与其他语言相比,Lisp 程序员更少关心语法的问题(与此相对应,查看一下 Pascal 的手册,就可以看到它将多少篇幅用于描述语法)。Lisp 对语法的藐视情况,部分地归因于它的灵活性,因此使它很容易改变表面的语法形式。此外还源自对许多“方便的”语法结构的看法。认为那样做产生出的语言更少统一性,在程序变得更大更复杂时,最终带来的麻烦比它们的价值更大。按照 Alan Perlis 的说法,“语法的糖衣会导致分号的癌症”。

12 Observe that there are two different operations being combined here: we are creating the procedure, and we are giving it the name square. It is possible, indeed important, to be able to separate these two notions -- to create procedures without naming them, and to give names to procedures that have already been created. We will see how to do this in section 1.3.2.

12可以看到,这里实际上组合了两个不同操作:建立了一个过程,并为它给定了名字 square。完全可能将这两个概念分离开,能够那样做也是非常重要的。我们可以创建一个过程但并不予以命名,也可以给以前创建好的过程命名。在 第 1.3.2 节里将会做这些事情。

13 Throughout this book, we will describe the general syntax of expressions by using italic symbols delimited by angle brackets -- e.g., <name> -- to denote the ``slots'' in the expression to be filled in when such an expression is actually used.

13在本书里描述表达式的语法形式时,用尖括号括起的斜体符号(例如 <name>)表示表达式中的一些“空位置”。在实际采用这种表达式时就需要填充它们。

14 More generally, the body of the procedure can be a sequence of expressions. In this case, the interpreter evaluates each expression in the sequence in turn and returns the value of the final expression as the value of the procedure application.

14更一般的情况是,过程体可以是一系列的表达式。此时解释器将顺序求值这个序列中的各个表达式,并将最后一个表达式的值作为整个过程应用的值并返回它。

15 Despite the simplicity of the substitution idea, it turns out to be surprisingly complicated to give a rigorous mathematical definition of the substitution process. The problem arises from the possibility of confusion between the names used for the formal parameters of a procedure and the (possibly identical) names used in the expressions to which the procedure may be applied. Indeed, there is a long history of erroneous definitions of substitution in the literature of logic and programming semantics. See Stoy 1977 for a careful discussion of substitution.

15虽然代换模型看起来似乎非常简单,但令人吃惊的是,给出代换过程的严格数学定义却异常复杂。问题在于,用作过程中形式参数的名字,可能会与该过程可能应用的那些表达式的(同样)名字相互混淆。在逻辑和程序设计的语义学文献里,关于代换的充满错误的定义有一个很长的历史,请参考 Stoy 1977 中有关代换的详细讨论。

16 In chapter 3 we will introduce stream processing, which is a way of handling apparently ``infinite'' data structures by incorporating a limited form of normal-order evaluation. In section 4.2 we will modify the Scheme interpreter to produce a normal-order variant of Scheme.

16第三章将引进流处理的概念,这是一种采用了正则序的受限形式去处理明显的“无限”数据结构的方式。在第 4.2 节将修改 Scheme 解释器,做出 Scheme 的一种采用正则序求值的变形。

17 ``Interpreted as either true or false'' means this: In Scheme, there are two distinguished values that are denoted by the constants #t and #f. When the interpreter checks a predicate's value, it interprets #f as false. Any other value is treated as true. (Thus, providing #t is logically unnecessary, but it is convenient.) In this book we will use names true and false, which are associated with the values #t and #f respectively.

17“解释为真或者假”的意思如下:在 Scheme 里存在这两个特殊的值,它们分别用常量 #t 和 #f 表示。当解释器检查一个谓词的值时,它将 #f 解释为假,而将所有其他的值都作为真(这样,提供 #t 在逻辑上就是不必要的,只是为了方便)。在本书中将使用 true 和 false,令它们分别着关联于 #t 和 #f。

18 abs also uses the ``minus'' operator -, which, when used with a single operand, as in (- x), indicates negation.

18 abs 还用到负号运算符“-”,这个运算符作用于一个对象时(例如写 (- x)),表示求出其负值。

19 A minor difference between if and cond is that the <e> part of each cond clause may be a sequence of expressions. If the corresponding <p> is found to be true, the expressions <e> are evaluated in sequence and the value of the final expression in the sequence is returned as the value of the cond. In an if expression, however, the <consequent> and <alternative> must be single expressions.

19在 if 和 cond 子句的 <e> 部分可以是一个表达式的序列,如果对应的 <p> 确定为真,<e> 中的表达式就会顺序地求值,并将其中最后一个表达式的值作为整个 cond 的值返回。而在 if 表达式里,<consequent> 和 <alternative> 都只能是单个表达式。

20 Declarative and imperative descriptions are intimately related, as indeed are mathematics and computer science. For instance, to say that the answer produced by a program is ``correct'' is to make a declarative statement about the program. There is a large amount of research aimed at establishing techniques for proving that programs are correct, and much of the technical difficulty of this subject has to do with negotiating the transition between imperative statements (from which programs are constructed) and declarative statements (which can be used to deduce things). In a related vein, an important current area in programming-language design is the exploration of so-called very high-level languages, in which one actually programs in terms of declarative statements. The idea is to make interpreters sophisticated enough so that, given ``what is'' knowledge specified by the programmer, they can generate ``how to'' knowledge automatically. This cannot be done in general, but there are important areas where progress has been made. We shall revisit this idea in chapter 4.

20说明性描述和行动性描述有着内在的联系,就像数学和计算机科学有着内在联系一样。举个例子,说一个程序产生的结果“正确”,就是给出了一个有关该程序性质的说明性语句。存在着大量的研究工作。其目标就是创建起一些技术,设法证明一个程序是正确的。在这一领域中有许多技术性困难,究其根源,都出自需要在行动性语句(程序是由它们构造起来的)和说明性语句(它们可以用于推导出某些结果)之间转来转去,在与此相关的研究分支里,有一个当前在程序设计语言设计领域中很重要的问题,那就是所谓的甚高级语言,在这种语言里编程就是写说明性的语句。这里的想法是将解释器做得足够复杂,程序员描述了需要“做什么”的知识之后,这种解释器就能自动产生出“怎样做”的知识。一般而言这是不可能做到的,但在这一领域已经取得了巨大进步,第四章我们将再来考虑这一想法。

21 This square-root algorithm is actually a special case of Newton's method, which is a general technique for finding roots of equations. The square-root algorithm itself was developed by Heron of Alexandria in the first century A.D. We will see how to express the general Newton's method as a Lisp procedure in section 1.3.4.

21这一平方根算法实际上是牛顿法的一个特例,牛顿法是一种寻找方程的根的通用技术。平方根算法本身是由亚历山大的 Heron 在公元一世纪提出的。我们将在第 1.3.4 节看到如何用 Lisp 描述一般性的牛顿法。

22 We will usually give predicates names ending with question marks, to help us remember that they are predicates. This is just a stylistic convention. As far as the interpreter is concerned, the question mark is just an ordinary character.

22我们将在谓词名字的最后用一个问号,以帮人注意到它们是谓词。这不过是一种风格上的约定。对于解释器而言,问题也就是一个普通的字符。

23 Observe that we express our initial guess as 1.0 rather than 1. This would not make any difference in many Lisp implementations. MIT Scheme, however, distinguishes between exact integers and decimal values, and dividing two integers produces a rational number rather than a decimal. For example, dividing 10 by 6 yields 5/3, while dividing 10.0 by 6.0 yields 1.6666666666666667. (We will learn how to implement arithmetic on rational numbers in section 2.1.1.) If we start with an initial guess of 1 in our square-root program, and x is an exact integer, all subsequent values produced in the square-root computation will be rational numbers rather than decimals. Mixed operations on rational numbers and decimals always yield decimals, so starting with an initial guess of 1.0 forces all subsequent values to be decimals.

23请注意,这里所用的初始猜测是 1.0 而不是 1。许多 Lisp 实现中,这样做并不会造成任何不同。MIT Scheme 区分了精确的整数和二进制数值,两个整数的商是一个有理数而不是二进制数值,例如,用 6 去除 10 将得到 5/3,而用 6.0 去除 10.0 得到的是 1.6666666666666667(我们将在第 2.1.1 节学习怎样实现有理数的算术运算)。如果我们用 1 作为平方根程序的初始猜测,x 就会骒一个精确的整数,随后在平方根过程里算出的所有值都将是有理数而不是二进制数值。对有理数和二进制数值的混合运算总是产生二进制数值。所以,开始时采用初始猜测 1.0,将迫使随后的所有结果都得到二进制的数值。

24 Readers who are worried about the efficiency issues involved in using procedure calls to implement iteration should note the remarks on ``tail recursion'' in section 1.2.1.

24关心通过过程调用来实现迭代时的效率问题的读者,可以去看第 1.2.1 节里有关“尾递归”的说明。

25 It is not even clear which of these procedures is a more efficient implementation. This depends upon the hardware available. There are machines for which the ``obvious'' implementation is the less efficient one. Consider a machine that has extensive tables of logarithms and antilogarithms stored in a very efficient manner.

25至于这两个过程中哪一个实现更有效,这一问题并不很明显,依赖于所使用的硬件。确实存在这样的机器。对于它们、其中那个“最明显的”实现效率更低一些。例如,考虑一种机器,它有一些范围很广的对数和反对数表,以某种非常有效的方式存放着。

26 The concept of consistent renaming is actually subtle and difficult to define formally. Famous logicians have made embarrassing errors here.

26统一换名的概念实际上也是很微妙的。很难形式地定义好。一些著名的逻辑学家也在这里犯过错误。

27 Lexical scoping dictates that free variables in a procedure are taken to refer to bindings made by enclosing procedure definitions; that is, they are looked up in the environment in which the procedure was defined. We will see how this works in detail in chapter 3 when we study environments and the detailed behavior of the interpreter.

27词法作用域要求过程中的自由变量实际引用外围过程定义中所出现的约束,也就是说,应该在定义本过程的环境中去寻找它们。我们将在第三章看到这种规定的细节工作情况,在那里我们将要研究环境的概念和解释器的一些行为细节。

28 Embedded definitions must come first in a procedure body. The management is not responsible for the consequences of running programs that intertwine definition and use.

28嵌套的定义必须出现在过程体之前。如果我们运行一个程序,但是其中的定义与使用混杂在一起,管理程序将不负任何责任。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值