[Racket] Language Model(一):Evaluation Model

Racket evaluation can be viewed as the simplification of expressions to obtain values. For example, just as an elementary-school student simplifies

Recket 的求值过程,可以看做是一个将表达式(expression)的化简为值(value)的过程。
就像小学生化简如下等式这样,

1 + 1 = 2

Racket evaluation simplifies

Racket求值过程如下,

(+ 1 1) -> 2

The arrow -> replaces the more traditional = to emphasize that evaluation proceeds in a particular direction toward simpler expressions. In particular, a value, such as the number 2, is an expression that evaluation simplifies no further.

我们用箭头 -> 代替了常用的等号 =,用来指明化简的方向。
我们称数字 2 是一个值(value),是因为它是不能再被化简表达式。

1. 子表达式的求值过程,续延(Sub-expression Evaluation and Continuations)

Some simplifications require more than one step. For example:

有些化简过程需要好几步,例如,

(- 4 (+ 1 1)) -> (- 4 2) -> 2

An expression that is not a value can always be partitioned into two parts: a redex (“reducible expression”), which is the part that can change in a single-step simplification (highlighted), and the continuation, which is the evaluation context surrounding the redex. In (- 4 (+ 1 1)), the redex is (+ 1 1), and the continuation is (- 4 []), where [] takes the place of the redex as it is reduced. That is, the continuation says how to “continue” after the redex is reduced to a value.

一个还未化简为值的表达式,总是可以划分出两个概念,
(1)可化简的表达式(redex),指的是在某次化简步骤中发生变化的那个表达式,例如上面的 (+ 1 1)
(2)续延(continuation),指的是与可化简表达式相关的求值环境。

对于(- 4 (+ 1 1))而言,可化简表达式(redex)是(+ 1 1),续延是(- 4 [])
其中 [] 占据了可化简表达式(redex)的位置,因此,续延表示的是可化简表达式(redex)化简后,接下来要做的事情。

Before some expressions can be evaluated, some or all of their sub-expressions must be evaluated. For example, in the application (- 4 (+ 1 1)), the application of - cannot be reduced until the sub-expression (+ 1 1) is reduced. Thus, the specification of each syntactic form specifies how (some of) its sub-expressions are evaluated and then how the results are combined to reduce the form away.

表达式求值之前,它的全部子表达式或一部分子表达式必须先求值。
例如(- 4 (+ 1 1)),在化简 - 之前,它的子表达式 (+ 1 1) 必须先被化简(reduce)。

因此,每一个句法形式(syntactic form),应指定它所有的子表达式该怎样求值,
并指定,它如何将这些值合并,得到最终结果。

The dynamic extent of an expression is the sequence of evaluation steps during which the expression contains the redex.

表达式的动态范围(dynamic extent),指的是当表达式包含可化简表达式(redex)时,表达式的求值序列。

2. 尾部位置(Tail Position)

An expression expr1 is in tail position with respect to an enclosing expression expr2 if, whenever expr1 becomes a redex, its continuation is the same as was the enclosing expr2’s continuation.

我们说表达式 expr1 位于包含它的表达式 expr2 的尾部位置,
是指 expr1expr2 的续延(continuation)相同。

For example, the (+ 1 1) expression is not in tail position with respect to (- 4 (+ 1 1)). To illustrate, we use the notation C[expr] to mean the expression that is produced by substituting expr in place of [] in some continuation C:

例如,表达式(+ 1 1)并没有位于(- 4 (+ 1 1))的尾部位置。
我们使用记号 C[expr] 将某个续延(continuation)C 中的 [],替换成 expr 得到的表达式。

C[(- 4 (+ 1 1))] -> C[(- 4 2)]

In this case, the continuation for reducing (+ 1 1) is C[(- 4 [])], not just C. The requirement specified in the first paragraph above is not met.

上面的例子中,(+ 1 1) 的续延(continuation)是C[(- 4 [])]
并不是(- 4 (+ 1 1))的续延 C,不符合尾部位置(Tail Position)的定义。

In contrast, (+ 1 1) is in tail position with respect to (if (zero? 0) (+ 1 1) 3) because, for any continuation C,

对比而言,(+ 1 1) 位于 (if (zero? 0) (+ 1 1) 3) 的尾部位置,对于任意的续延(continuation)C,我们有,

C[(if (zero? 0) (+ 1 1) 3)] -> C[(if #t (+ 1 1) 3)] -> C[(+ 1 1)]

The requirement specified in the first paragraph is met. The steps in this reduction sequence are driven by the definition of if, and they do not depend on the continuation C. The “then” branch of an if form is always in tail position with respect to the if form. Due to a similar reduction rule for if and #f, the “else” branch of an if form is also in tail position.

可见,它满足了上述尾部位置(Tail Position)的定义。
以上表达式的化简过程与 if 的定义有关,而与具体的续延(continuation)C 无关。

if 表达式的 then 分支也总是位于 if 的尾部位置,
我们可以用类似的方法证明,只需设条件为 #f 即可。

Tail-position specifications provide a guarantee about the asymptotic space consumption of a computation. In general, the specification of tail positions accompanies the description of each syntactic form, such as if.

尾部位置(Tail Position)保证了计算的渐进空间复杂度。
通常,每个句法形式(syntactic form)都要单独定义自己的尾部位置,例如 if

3. 一次返回多个值(Multiple Return Values)

A Racket expression can evaluate to multiple values, to provide symmetry with the fact that a procedure can accept multiple arguments.

Racket 表达式的求值结果可以是多个值,为了提供一种对称性,因为函数可以接受多个参数。

Most continuations expect a certain number of result values, although some continuations can accept an arbitrary number. Indeed, most continuations, such as (+ [] 1), expect a single value. The continuation (let-values ([(x y) []]) expr) expects two result values; the first result replaces x in the body expr, and the second replaces y in expr. The continuation (begin [] (+ 1 2)) accepts any number of result values, because it ignores the result(s).

大部分续延(continuation)期望接受固定数目的值,而有些续延(continuation)则可以接受任意数目的值。
事实上,大部分续延(continuation)例如 (+ [] 1),只接受一个值。

续延(continuation)(let-values ([(x y) []]) expr) 接受两个值,
第一个值将替换掉 expr 中的所有 x,第二个值,则替换 expr 中的所有 y

续延(continuation)(begin [] (+ 1 2)),接受任意数目的值,因为它会忽略这些值。

In general, the specification of a syntactic form indicates the number of values that it produces and the number that it expects from each of its sub-expressions. In addition, some procedures (notably values) produce multiple values, and some procedures (notably call-with-values) create continuations internally that accept a certain number of values.

一般而言,句法形式(syntactic form)的规范中,指明了它会得到几个值,以及它期望每个子表达式产生几个值。

值得一提的是,有些函数,例如values可以产生多个值,
而有些函数,例如call-with-values会创建一个能接受多个值的续延(continuation)。

4. 顶层变量(Top-Level Variables)

Given x = 10 then an algebra student simplifies x + 1 as follows:

假设 x = 10,那么代数学告诉我们,x + 1 可化简为,

x + 1 = 10 + 1 = 11

Racket works much the same way, in that a set of top-level variables are available for substitutions on demand during evaluation. For example, given

Racket 的执行过程与此相同,在求值过程中,遇到的顶层变量(Top-Level Variables)会被替换为它的值。例如,

(define x 10)

then
将这样求值,

(+ x 1) -> (+ 10 1) -> 11

In Racket, the way definitions are created is just as important as the way they are used. Racket evaluation thus keeps track of both definitions and the current expression, and it extends the set of definitions in response to evaluating forms such as define.

Racket 中,定义的创建方式跟它们的使用方式一样重要,
Racket 在求值过程中会跟踪所有的定义,把所有定义收集起来。

Each evaluation step, then, transforms the current set of definitions and program into a new set of definitions and program. Before a define can be moved into the set of definitions, its expression (i.e., its right-hand side) must be reduced to a value. (The left-hand side is not an expression position, and so it is not evaluated.)

每进行一步求值动作,Racket 会改变这些定义的状态。
一个 define 表达式被移入定义集合的时候,变量所表示的表达式必须先化简为一个值。

   defined:
   evaluate: (begin (define x (+ 9 1)) (+ x 1))
-> defined:
   evaluate: (begin (define x 10) (+ x 1))
-> defined:  (define x 10)
   evaluate: (begin (void) (+ x 1))
-> defined:  (define x 10)
   evaluate: (+ x 1)
-> defined:  (define x 10)
   evaluate: (+ 10 1)
-> defined:  (define x 10)
   evaluate: 11

Using set!, a program can change the value associated with an existing top-level variable:

通过使用 set!,程序可以改变顶层变量(Top-Level Variables)的值。

   defined:  (define x 10)
   evaluate: (begin (set! x 8) x)
-> defined:  (define x 8)
   evaluate: (begin (void) x)
-> defined:  (define x 8)
   evaluate: x
-> defined:  (define x 8)
   evaluate: 8

5. 对象和命令式更新(Objects and Imperative Update)

In addition to set! for imperative update of top-level variables, various procedures enable the modification of elements within a compound data structure. For example, vector-set! modifies the content of a vector.

类似于 set! 可以采用命令式的方式更新顶层变量,
还有很多个函数,可以用来修改复合数据结构中的组成部分。
例如,vector-set! 可以用来修改向量中的元素。

To explain such modifications to data, we must distinguish between values, which are the results of expressions, and objects, which hold the data referenced by a value.

为了解释这种对数据的修改方式,我们首先得区分值和对象(object),
值,指的是表达式的求值结果;而对象,也是一个值,表示数据的引用。

A few kinds of objects can serve directly as values, including booleans, (void), and small exact integers. More generally, however, a value is a reference to an object stored somewhere else. For example, a value can refer to a particular vector that currently holds the value 10 in its first slot. If an object is modified via one value, then the modification is visible through all the values that reference the object.

有很多种类的对象,可以直接看做值,包括布尔对象,(void),以及小的精确数字。
更一般化而言,值,引用了存在其他地方的一个对象。

例如,值可以表示一个向量,该向量的首元素为 10
如果对象经由指向它的某个值修改了,那么这个修改将对所有引用它的值可见。

In the evaluation model, a set of objects must be carried along with each step in evaluation, just like the definition set. Operations that create objects, such as vector, add to the set of objects:

在求值模型中,每一个步骤都要带着所有的对象一起执行求值,就像顶层变量的定义那样。
创建对象的操作,例如vector,会向集合中添加新的对象。

   objects:
   defined:
   evaluate: (begin (define x (vector 10 20))
                    (define y x)
                    (vector-set! x 0 11)
                    (vector ref y 0))
-> objects:  (define <o1> (vector 10 20))
   defined:
   evaluate: (begin (define x <o1>)
                    (define y x)
                    (vector-set! x 0 11)
                    (vector-ref y 0))
-> objects:  (define <o1> (vector 10 20))
   defined:  (define x <o1>)
   evaluate: (begin (void)
                    (define y x)
                    (vector-set! x 0 11)
                    (vector-ref y 0))
-> objects:  (define <o1> (vector 10 20))
   defined:  (define x <o1>)
   evaluate: (begin (define y x)
                    (vector-set! x 0 11)
                    (vector-ref y 0))
-> objects:  (define <o1> (vector 10 20))
   defined:  (define x <o1>)
   evaluate: (begin (define y <o1>)
                    (vector-set! x 0 11)
                    (vector-ref y 0))
-> objects:  (define <o1> (vector 10 20))
   defined:  (define x <o1>)
             (define y <o1>)
   evaluate: (begin (void)
                    (vector-set! x 0 11)
                    (vector-ref y 0))
-> objects:  (define <o1> (vector 10 20))
   defined:  (define x <o1>)
             (define y <o1>)
   evaluate: (begin (vector-set! x 0 11)
                    (vector-ref y 0))
-> objects:  (define <o1> (vector 10 20))
   defined:  (define x <o1>)
             (define y <o1>)
   evaluate: (begin (vector-set! <o1> 0 11)
                    (vector-ref y 0))
-> objects:  (define <o1> (vector 11 20))
   defined:  (define x <o1>)
             (define y <o1>)
   evaluate: (begin (void)
                    (vector-ref y 0))
-> objects:  (define <o1> (vector 11 20))
   defined:  (define x <o1>)
             (define y <o1>)
   evaluate: (vector-ref y 0)
-> objects:  (define <o1> (vector 11 20))
   defined:  (define x <o1>)
             (define y <o1>)
   evaluate: (vector-ref <o1> 0)
-> objects:  (define <o1> (vector 11 20))
   defined:  (define x <o1>)
             (define y <o1>)
   evaluate: 11

The distinction between a top-level variable and an object reference is crucial. A top-level variable is not a value, so it must be evaluated. Each time a variable expression is evaluated, the value of the variable is extracted from the current set of definitions. An object reference, in contrast, is a value and therefore needs no further evaluation. The evaluation steps above use angle-bracketed <o1> for an object reference to distinguish it from a variable name.

顶层变量和对象引用是求值规则是不同的。
顶层变量不是一个值,使用是时候必须先求值,变量每次求值,它的值都从变量集中获取。
而对象引用是一个值,不再需要进一步求值了,

上述示例的求值过程中,我们使用了 <o1> 表示了对象的引用,用以区分与变量名字的不同。

An object reference can never appear directly in a text-based source program. A program representation created with datum->syntax, however, can embed direct references to existing objects.

对象引用,不能直接出现在基于文本源程序中。
使用 datum->syntax 创建的程序,可以向现存对象中嵌入直接引用。

6. 对象的相等性和比较(Object Identity and Comparisons)

The eq? operator compares two values, returning #t when the values refer to the same object. This form of equality is suitable for comparing objects that support imperative update (e.g., to determine that the effect of modifying an object through one reference is visible through another reference). Also, an eq? test evaluates quickly, and eq?-based hashing is more lightweight than equal?-based hashing in hash tables.

eq? 操作符对比两个值,如果这两个值指向同一个对象,就返回#t
这种形式的相等性很适合对比对象,尤其是支持命令式更新的对象。

并且,eq? 测试的速度非常快,eq?equal? 使用的哈希表更加轻量级。

In some cases, however, eq? is unsuitable as a comparison operator, because the generation of objects is not clearly defined. In particular, two applications of + to the same two exact integers may or may not produce results that are eq?, although the results are always equal?. Similarly, evaluation of a lambda form typically generates a new procedure object, but it may re-use a procedure object previously generated by the same source lambda form.

有些场景 eq? 并不合适,因为对象的创建机制并没有被明确定义。
比如,对两个整数进行加法运算两次,可能会产生不一样的对象,它们是 equal? 的,但可能并不一定 eq?
类似的,每次求值lambda可能会生成不同的函数对象,也可能复用前一次创建的函数对象。

The behavior of a datatype with respect to eq? is generally specified with the datatype and its associated procedures.

特定数据类型的行为表现,取决于该数据类型关于eq?的定义。

7. 垃圾回收(Garbage Collection)

In the program state

以下程序状态中,

objects:  (define <o1> (vector 10 20))
          (define <o2> (vector 0))
defined:  (define x <o1>)
evaluate: (+ 1 x)

evaluation cannot depend on <o2>, because it is not part of the program to evaluate, and it is not referenced by any definition that is accessible by the program. The object is said to not be reachable. The object <o2> may therefore be removed from the program state by garbage collection.

求值过程并不涉及<o2>,因为它并没有在待求值的程序中出现,
并且,它也没有被任何其他的可访问的程序所引用,这样的对象称为不可达对象(not be reachable)。
因此,<o2>对象可以被垃圾回收器(garbage collection)从程序状态中删除了。

A few special compound datatypes hold weak references to objects. Such weak references are treated specially by the garbage collector in determining which objects are reachable for the remainder of the computation. If an object is reachable only via a weak reference, then the object can be reclaimed, and the weak reference is replaced by a different value (typically #f).

一些特殊的复合数据类型,保存了对象的弱引用(weak references)。
垃圾回收期,会对这些弱引用进行特殊处理,在后续计算的过程中,会考察弱引用对象的可达性。
如果一个对象只被弱引用,那么这个对象就可以被回收,对象的弱引用就被替换成了一个不同的值,一般为 #f

As a special case, a fixnum is always considered reachable by the garbage collector. Many other values are always reachable due to the way they are implemented and used: A character in the Latin-1 range is always reachable, because equal? Latin-1 characters are always eq?, and all of the Latin-1 characters are referenced by an internal module. Similarly, null, #t, #f, eof, and #<void> and are always reachable. Values produced by quote remain reachable when the quote expression itself is reachable.

作为一个特殊场景,定长数字(fixnum)不被垃圾回收,被认为总是可达。

其他一些总是可达的值,取决于它们的实现和使用方式:
Latin-1 范围内的字符,总是可达的,因为这个范围 equal? 的字符总是 eq? 的,
而且所有 Latin-1 范围内的字符都被内部模块引用了。

类似的,null, #t, #f, eof#<void> 总是可达的。

quote产生的值是可达的,仅当quote表达式本身的可达的。

8. 函数调用和局部变量(Procedure Applications and Local Variables)

Given f(x) = x + 10 an algebra student simplifies f(7) as follows:

假设 f(x) = x + 10,应用代数学知识,我们可以这样化简 f(7)

f(7) = 7 + 10 = 17

The key step in this simplification is to take the body of the defined function f and replace each x with the actual value 7.

以上化简过程中的关键步骤是,将 f 函数体中的每一个 x 替换成 7

Racket procedure application works much the same way. A procedure is an object, so evaluating (f 7) starts with a variable lookup:

Racket 函数调用过程与此相同。
Racket 函数(procedure)是一个对象,所以求值 (f 7) 之前,要先进行变量查找。

   objects:  (define <p1> (lambda (x) (+ x 10)))
   defined:  (define f <p1>)
   evaluate: (f 7)
-> objects:  (define <p1> (lambda (x) (+ x 10)))
   defined:  (define f <p1>)
   evaluate: (<p1> 7)

Unlike in algebra, however, the value associated with a procedure argument variable can be changed in the body of a procedure by using set!, as in the example (lambda (x) (begin (set! x 3) x)). Since the value associated with argument variable x can be changed, the value cannot be substituted for x when the procedure is first applied.

然而,与代数学不同的是,Racket 函数在执行过程中,它的参数值是可以使用 set! 修改的。
例如 (lambda (x) (begin (set! x 3) x))
由于参数变量 x 的值可被修改,因此 x 不能在函数刚被调用的时候替换为它的值。

Instead, a new location is created for each variable on each application. The argument value is placed in the location, and each instance of the variable in the procedure body is replaced with the new location:

所以,每次函数调用对每个变量,都要创建一个新的存储位置(location)。
函数体中的相同变量,被替换为相同的存储位置。

   objects:  (define <p1> (lambda (x) (+ x 10)))
   defined:  (define f <p1>)
   evaluate: (<p1> 7)
-> objects:  (define <p1> (lambda (x) (+ x 10)))
   defined:  (define f <p1>)
             (define xloc 7)
   evaluate: (+ xloc 10)
-> objects:  (define <p1> (lambda (x) (+ x 10)))
   defined:  (define f <p1>)
             (define xloc 7)
   evaluate: (+ 7 10)
-> objects:  (define <p1> (lambda (x) (+ x 10)))
   defined:  (define f <p1>)
             (define xloc 7)
   evaluate: 17

A location is the same as a top-level variable, but when a location is generated, it (conceptually) uses a name that has not been used before and that cannot be generated again or accessed directly.

存储位置(location)和顶层变量相同,只是当存储位置创建时,它会使用一个未被占用的名字,
这个名字不会再次出现,也不能直接访问。

Generating a location in this way means that set! evaluates for local variables, including argument variables, in the same way as for top-level variables, because the local variable is always replaced with a location by the time the set! form is evaluated:

用这样的方式生成局部变量,意味着包括函数参数在内,set! 的工作方式与顶层变量相同。
因为局部变量总是会在 set! 调用之前被替换为了存储位置。

   objects:  (define <p1> (lambda (x) (begin (set! x 3) x)))
   defined:  (define f <p1>)
   evalute:  (f 7)
-> objects:  (define <p1> (lambda (x) (begin (set! x 3) x)))
   defined:  (define f <p1>)
   evalute:  (<p1> 7)
-> objects:  (define <p1> (lambda (x) (begin (set! x 3) x)))
   defined:  (define f <p1>)
             (define xloc 7)
   evalute:  (begin (set! xloc 3) xloc))
-> objects:  (define <p1> (lambda (x) (begin (set! x 3) x)))
   defined:  (define f <p1>)
             (define xloc 3)
   evalute:  (begin (void) xloc))
-> objects:  (define <p1> (lambda (x) (begin (set! x 3) x)))
   defined:  (define f <p1>)
             (define xloc 3)
   evalute:  xloc
-> objects:  (define <p1> (lambda (x) (begin (set! x 3) x)))
   defined:  (define f <p1>)
             (define xloc 3)
   evalute:  3

The location-generation and substitution step of procedure application requires that the argument is a value. Therefore, in ((lambda (x) (+ x 10)) (+ 1 2)), the (+ 1 2) sub-expression must be simplified to the value 3, and then 3 can be placed into a location for x. In other words, Racket is a call-by-value language.

函数调用时的存储位置生成和替换过程,需要参数是一个值。
因此,在 ((lambda (x) (+ x 10)) (+ 1 2)) 中,子表达式 (+ 1 2) 必须先被求值为 3,
然后 3 才能被设置为 x 存储位置的值。

换言之,Racket 是一个传值调用的语言。

Evaluation of a local-variable form, such as (let ([x (+ 1 2)]) expr), is the same as for a procedure call. After (+ 1 2) produces a value, it is stored in a fresh location that replaces every instance of x in expr.

局部变量的求值,与函数调用类似,例如 (let ([x (+ 1 2)]) expr)
先求值 (+ 1 2),它的值会被保存到一个新的存储位置,然后 expr 中的所有 x 都会替换为这个存储位置。

9. 变量和存储位置(Variables and Locations)

A variable is a placeholder for a value, and expressions in an initial program refer to variables. A top-level variable is both a variable and a location. Any other variable is always replaced by a location at run-time; thus, evaluation of expressions involves only locations. A single local variable (i.e., a non-top-level, non-module-level variable), such as an argument variable, can correspond to different locations during different applications.

变量是值的占位符,程序中的表达式中会包含变量。
顶层变量既是变量,又是存储位置,除此之外的所有变量总是会在运行时被替换为存储位置。

因此,表达式的求值过程,只会涉及存储位置,
一个局部变量(即,不是顶层变量,不是模块级变量)例如函数参数,在函数多次调用时,会关联到不同的存储位置。

For example, in the program
比如对于以下程序,

(define y (+ (let ([x 5]) x) 6))

both y and x are variables. The y variable is a top-level variable, and the x is a local variable. When this code is evaluated, a location is created for x to hold the value 5, and a location is also created for y to hold the value 11.

yx 都是变量,其中 y 是顶层变量,x是局部变量。
以上代码在求值的时,会为 x 创建一个值为 5 的存储位置,也会为 y 创建一个值为 11 的存储位置。

The replacement of a variable with a location during evaluation implements Racket’s lexical scoping. For example, when an argument variable x is replaced by the location xloc, it is replaced throughout the body of the procedure, including any nested lambda forms. As a result, future references to the variable always access the same location.

Racket 在求值过程中,将变量替换为存储位置,实现了词法作用域(lexical scoping)。
例如,当一个函数参数 x 被替换为存储位置 xloc,整个函数体中的 x 都会被替换,包括任何内嵌的 lambda
结果以后所有对 x 的引用,都会访问同一个存储位置。

10. 模块和模块级变量(Modules and Module-Level Variables)

Most definitions in Racket are in modules. In terms of evaluation, a module is essentially a prefix on a defined name, so that different modules can define the same name. That is, a module-level variable is like a top-level variable from the perspective of evaluation.

Racket 中大部分定义是包含在模块中的。
从求值角度来看,模块本质上是给已定义的名字加上了前缀,因此不同的模块可以定义相同的名字。
这样看来,模块级变量与顶层变量的求值方式是一样的。

One difference between a module and a top-level definition is that a module can be declared without instantiating its module-level definitions. Evaluation of a require instantiates (i.e., triggers the instantiation of) the declared module, which creates variables that correspond to its module-level definitions.

模块级变量和顶层变量的不同之一在于,模块可以在初始化模块级定义之前进行声明。
求值 require 时,才会初始化事先声明的模块,创建模块中定义的变量。

For example, given the module declaration

例如,考虑以下的模块定义,

(module m racket
  (define x 10))

the evaluation of (require 'm) creates the variable x and installs 10 as its value. This x is unrelated to any top-level definition of x (as if it were given a unique, module-specific prefix).

求值 (require 'm) 会创建变量 x,并设置它的值为 10
这个 x 跟任何其他的顶层定义的 x 不同,因为它携带了一个独一无二的模块前缀。

(1)阶段(Phases)

The purpose of phases is to address the necessary separation of names defined at execution time versus names defined at expansion time.

引入阶段(phases)这个概念的用意是,对运行时定义的名字以及宏展开时定义的名字进行区分。

A module can be instantiated in multiple phases. A phase is an integer that, like a module name, is effectively a prefix on the names of module-level definitions. Phase 0 is the execution-time phase.

模块可以在多个阶段初始化。阶段用数字表示,并且跟模块名一样实际上是模块级定义名字的一个前缀。
0 阶段,表示的是运行时阶段。

A top-level require instantiates a module at phase 0, if the module is not already instantiated at phase 0. A top-level (require (for-syntax ....)) instantiates a module at phase 1 (if it is not already instantiated at that phase); for-syntax also has a different binding effect on further program parsing.

顶层调用 require 会于第 0 阶段初始化某个模块(如果这个模块还没有在第 0 阶段初始化过的话)。
顶层调用 (require (for-syntax ....)) 会与第 1 阶段初始化某个模块(如果这个模块还没在第 1 阶段初始化)。

for-syntax 会有不同的绑定行为,主要用于后续的程序变换中。

Within a module, some definitions are already shifted by a phase: the begin-for-syntax form is similar to begin, but it shifts expressions and definitions by a relative phase +1. Likewise, the define-for-syntax form is similar to define, but shifts the definition by +1. Thus, if the module is instantiated at phase 1, the variables defined with begin-for-syntax are created at phase 2, and so on. Moreover, this relative phase acts as another layer of prefixing, so that x defined with define and x defined with define-for-syntax can co-exist in a module without colliding. A begin-for-syntax form can be nested within a begin-for-syntax form, in which case the inner definitions and expressions are in relative phase +2, and so on. Higher phases are mainly related to program parsing instead of normal evaluation.

在模块中,某些定义会对阶段进行改变。
begin-for-syntaxbegin 很相似,但是它调用的表达式,会比当前阶段向前增加一个阶段。
类似的还有 define-for-syntaxdefine,前者也会增加一个阶段。
即,如果当前模块于第 1 阶段初始化,那么 begin-for-syntax 定义的变量会于第 2 阶段创建。

相对阶段扮演着另一级前缀,因此,define 定义的 x,与 define-for-syntax 定义的 x 可以在一个模块中并存。

begin-for-syntax 可嵌套位于另一个 begin-for-syntax 中,
这时里面的定义和表达式,会相对于模块而言,增加两个阶段。

更高的阶段,与求值过程无关,主要跟程序的解析和转换有关。

If a module instantiated at phase n requires another module, then the required module is first instantiated at phase n, and so on transitively. (Module requires cannot form cycles.) If a module instantiated at phase n requires another module M for-syntax, then M becomes available at phase n+1, and it later may be instantiated at phase n+1. If a module that is available at phase n (for n>0) requires another module M for-template, then M becomes available at phase n-1, and so on. Instantiations of available modules above phase 0 are triggered on demand.

一个于第 n 阶段初始化的模块 require 其他模块的时候,该模块会于第 n 阶段初始化。
而如果使用for-syntax require 其他模块 M 的话,则模块 M 会于第 n+1 阶段可用,进而被初始化。
如果使用 for-template require 模块 M,则 M 会于第 n-1 阶段可用,进而被初始化。

高于第 0 阶段的可用模块,会按需初始化。

A final distinction among module instantiations is that multiple instantiations may exist at phase 1 and higher. These instantiations are created by the parsing of module forms, and are, again, conceptually distinguished by prefixes.

关于模块初始化值得关注的最后一点是,同一模块可能在第 1 阶段或者更高阶段的模块都被初始化了,
它们会产生不同的模块前缀。

Top-level variables can exist in multiple phases in the same way as within modules. For example, define within begin-for-syntax creates a phase 1 variable. Furthermore, reflective operations like make-base-namespace and eval provide access to top-level variables in higher phases, while module instantiations (triggered by require) relative to such top-levels are in correspondingly higher phases.

模块中的顶层变量可以出现在多个阶段中。
例如,begin-for-syntax 中的 define 会创建一个第 1 阶段的变量。

此外,当模块初始化(使用 require 触发的)相对于这些顶层变量而言,位于更高的阶段的时候,
反射操作,比如 make-base-namespaceeval 提供了访问更高阶段顶层变量的机会。

(2)保障分离编译(The Separate Compilation Guarantee)

When a module is compiled, its phase 1 is instantiated. This can, in turn, trigger the transitive instantiation of many other modules at other phases, including phase 1. Racket provides a very strong guarantee about this instantiation called “The Separate Compilation Guarantee”:

当模块被编译时,它就在第 1 阶段被初始化了。
这会相继触发其他模块,位于其他阶段的初始化(包括第 1 阶段)。

Racket 提供了一个强有力支持,以保障分离编译。

Any effects of the instantiation of the module’s phase 1 due to compilation on the Racket runtime system are discarded.

在编译过程中,模块在第 1 阶段的初始化时的任何效应,Racket 的运行时系统都会丢弃。

The guarantee concerns effects. There are two different kinds of effects: internal and external.

以上提到的效应(effect)包括两种:内部效应和外部效应。

Internal effects are exemplified by mutation. Mutation is the action of a function such as set-box!, which changes the value contained in the box. The modified box is not observable outside Racket, so the effect is said to be “internal.” By definition, internal effects are not detectable outside the Racket program.

内部效应指的是可变性,例如 set-box! 它会修改 box 数据类型中的值。
修改后的 box 对 Racket 外的世界是不可见的,因此被称为内部效应。
内部效应无法被 Racket 程序之外的世界检测到。

External effects are exemplified by input/output (I/O). I/O is the action of a function such as tcp-connect, which communicates with the operating system to send network packets outside the machine running Racket. The transmission of these packets is observable outside Racket, in particular by the receiving computer or any routers in between. External effects exist to be detectable outside the Racket program and are often detectable using physical processes.

外部效应的典型例子是输入输出操作,
例如 tcp-connect,它会与操作系统交互,向其他计算机发送网络请求。
网络请求可被 Racket 程序之外的世界检测到,比如接受方的计算机,或它们之间的路由器。

外部效应通常可以被物理过程检测到。

An effect is discarded when it is no longer detectable. For instance, the mutation of a box from 3 to 4 is discarded when it ceases to be detectable that it was ever changed and thus would still contain 3. Because external effects are intrinsically observable outside Racket, they are irreversible and cannot be discarded.

当效应不可检测时就会被丢弃。
例如,将 box 中的值从 3 改成 4 会被丢弃,因为它引起的改变不可检测,因此改 box 中个只仍然为 3

由于外部效应本质上从 Racket 外部可见,并且是不可复原的,因此无法被丢弃。

Thus, The Separate Compilation Guarantee only concerns effects like mutation, because they are exclusively effects “on the Racket runtime system” and not “on the physical universe.”

分离编译保障仅关心那些类似于变量改变之类的效应,
因为它们只是 Racket 运行时系统内的效应,而不是外部物理世界中的效应。

There are many things a Racket program can do that appear to be internal effects but are actually external effects. For instance, bytes-set! is typically an internal effect, except when the bytes are created by make-shared-bytes, which allocates in space observable by other processes. Thus, effects which modify those bytes are not discardable, so bytes-set!, in this case, has an external effect.

Racket 程序中有很多看起来像内部效应的事情,其实产生了外部效应。
例如,bytes-set! 一般而言产生的是内部效应,除非被改变的字节是由 make-shared-bytes 创建的,
这样创建的字节,可以被其他进程观测到。

因此,这样对字节的修改无法被丢弃,在这种情况下 bytes-set! 是具有外部效应的。

The opposite is also true: some things which appear to be external are actually internal. For instance, if a Racket program starts multiple threads and uses mutation to communicate between them, that mutation is purely internal, because Racket’s threads are defined entirely internally (they are not related to operating system threads).

相反的场景也有,有些事情看起来是具有外部效应的,但实际是内部效应。
例如,一个 Racket 程序启动了多个进程,然后通过进程间通信,在它们之间做出变量修改,
这样的变量修改,就是内部效应的,因为这些进程完全是由 Racket 创建的。

Furthermore, whenever a Racket program calls an unsafe function, the Racket runtime system makes no promises about its effects. For instance, all foreign calls use ffi/unsafe, so all foreign calls are unsafe and their effects cannot be discarded by Racket.

此外,如果一个 Racket 程序调用了一个不安全的函数,Racket 的运行时系统就不能保证它产生什么效应。
例如,使用 ffi/unsafe 进行的所有外部调用,因此所有的外部调用都不能被丢弃。

Finally, The Separate Compilation Guarantee only concerns instantiations at phase 1 during compilation and not all phase 1 instantiations generally, such as when its phase 1 is required and used for effects via reflective mechanisms.

最后,分离编译保障只关心编译过程中,于第 1 阶段初始化的模块,
而不是所有于第 1 阶段初始化的模块。
比如,那些通过反射机制访问的第 1 阶段初始化的模块。

The practical consequence of this guarantee is that because effects are never visible, no module can detect whether a module it requires is already compiled. Thus, it can never change the compilation of one module to have already compiled a different module. In particular, if module A is shared by the phase 1 portion of modules X and Y, then any internal effects while X is compiled are not visible during the compilation of Y, regardless of whether X and Y are compiled during the same execution of Racket’s runtime system and regardless of the order of compilation.

分离编译保障的实际意义是,模块无法检测它引用的模块是否已经被编译了。
因此,将一个模块进行编译,并不会造成其他影响。

如果模块 A 于第 1 阶段被模块 XY 共享了,
则编译过程中 X 中的所有内部效应,对 Y 来说都是不可见的,
不管它们是否由 Racket 运行时系统一起编译的,也不管它们的编译顺序如何。

The following set of modules demonstrate this guarantee. First, we define a module with the ability to observe effects via a box:

以下一些模块可作为例子,来解释分离编译保障。
首先,我们定义一个可以观察到 box 的模块。

(module box racket/base
  (provide (all-defined-out))
  (define b (box 0)))

Next, we define two syntax transformers that use and mutate this box:

然后,我们定义两个语法变换,用以使用和修改 box

(module transformers racket/base
  (provide (all-defined-out))
  (require (for-syntax racket/base 'box))
  (define-syntax (sett stx)
    (set-box! b 2)
    #'(void))
  (define-syntax (gett stx)
    #`#,(unbox b)))

Next, we define a module that uses these transformers:

接着,再定义一个引用了 transformers 的模块,

(module user racket/base
  (provide (all-defined-out))
  (require 'transformers)
  (sett)
  (define gott (gett)))

Finally, we define a second module that uses these transformers and the user module:

最后,我们定义一个引用了 transformersuser 的模块,

(module test racket/base
  (require 'box 'transformers 'user)
  (displayln gott)
  (displayln (gett))
  (sett)
  (displayln (gett))
  (displayln (unbox b)))

This module displays:

这个模块会依次展示如下信息,

  • 2, because the (gett) in module user expanded to 2.
    2,因为 user 模块中的 (gett) 会展开为 2

  • 0, because the effects of compiling user were discarded.
    0,因为编译 user 的效应被丢弃了。

  • 2, because the effect of (sett) inside test has not yet been discarded.
    2,因为 test 模块中 (sett) 的效应没有被丢弃。

  • 0, because the effects of sett at phase 1 are irrelevant to the phase 0 use of b in (unbox b).
    0,因为位于第 1 阶段 sett 的效应,与 (unbox b) 中的 b (位于第 0 阶段)无关。

Furthermore, this display will never change, regardless of which order these modules are compiled in or whether they are compiled at the same time or separately.

并且,不论这些模块的编译顺序如何,不管它们是同时编译还是分开编译,
这些展示信息都不会改变。

In contrast, if these modules were changed to store the value of b in a file on the filesystem, then the program would only display 2.

不过,如果 b 都保存到文件中,展示信息就会改变了,都会展示 2

The Separate Compilation Guarantee is described in more detail in the paper “Composable and Compilable Macros” [Flatt02], including informative examples. The paper “Advanced Macrology and the implementation of Typed Scheme” [Culpepper07] also contains an extended example of why it is important and how to design effectful syntactic extensions in its presence.

分离编译保障相关的资料可以查阅论文,“Composable and Compilable Macros”。
“Advanced Macrology and the implementation of Typed Scheme” 中也包含了一个扩展例子,
介绍了为什么它是重要的,以及如何开发高效的语法扩展。

(3)跨阶段持久化的模块(Cross-Phase Persistent Modules)

Module declarations that fit a highly constrained form —— including a (#%declare #:cross-phase-persistent) form in the module body —— create cross-phase persistent modules. A cross-phase persistent module’s instantiations across all phases share the variables produced by the first instantiation of the module. Additionally, cross-phase persistent module instantiations persist across module registries when they share a common module declaration.

命名模块式,包含 (#%declare #:cross-phase-persistent) 的话,
就会创建一个跨阶段持久化的模块,它可以跨越所有的阶段共享变量,这些变量只在模块第一次初始化时创建。

除此之外,跨阶段持久化模块,采用了跨模块的方式注册,共享同一个模块声明。

Examples:

例如,

> (module cross '#%kernel
    (#%declare #:cross-phase-persistent)
    (#%provide x)
    (define-values (x) (gensym)))
> (module noncross '#%kernel
    (#%provide x)
    (define-values (x) (gensym)))
> (define ns (current-namespace))
> (define (same-instence? mod)
    (namespace-require mod)
    (define a
      (parameterize ([current-namespace (make-base-namespace)])
        (namespace-attach-module-declaration ns mod)
        (namespace-require mod)
        (namespace-variable-value 'x)))
    (define b
      (parameterize ([current-namespace (make-base-namespace)])
        (namespace-attach-module-declaration ns mod)
        (namespace-require mod)
        (namespace-variable-value 'x)))
    (eq? a b))
> (same-instence? ''noncross)
#f
> (same-instence? ''cross)
#t

The intent of a cross-phase persistent module is to support values that are recognizable after phase crossings. For example, when a macro transformer running in phase 1 raises a syntax error as represented by an exn:fail:syntax instance, the instance is recognizable by a phase-0 exception handler wrapping a call to eval or expand that triggered the syntax error, because the exn:fail:syntax structure type is defined by a cross-phase persistent module.

跨阶段持久化模块的意图是,支持跨阶段可用的值。
例如,当一个宏转换器于第 1 阶段运行时,报了一个语法错误,表示为了 exn:fail:syntax 的实例,
该实例,可以被一个第 0 阶段的异常处理器捕获到,通过使用 evalexpand,
这是因为 exn:fail:syntax 数据类型被定义在跨阶段持久化的模块中。

A cross-phase persistent module imports only other cross-phase persistent modules, and it contains only definitions that bind variables to functions, structure types and related functions, or structure-type properties and related functions. A cross-phase persistent module never includes syntax literals (via quote-syntax) or variable references (via #%variable-reference).

跨阶段持久化的模块,只能导入其他同样是跨阶段持久化的模块,
并且,只能包含绑定为函数的变量,structure 数据类型以及相关的函数,或者 structure 类型的属性及相关的函数。

跨阶段持久化模块中,不能包含语法字面量(通过 quote-syntax 引入的),
或者变量引用(通过 #%variable-reference 引入的)。

A documented module should be assumed non–cross-phase persistent unless it is specified as cross-phase persistent (such as racket/kernel).

文档中写的模块,默认不是跨阶段持久化的,除非被显示标记为跨阶段持久化的。
例如 racket/kernel

(4)模块重命名(Module Redeclarations)

When a module is declared using a name with which a module is already declared, the new declaration’s definitions replace and extend the old declarations. If a variable in the old declaration has no counterpart in the new declaration, the old variable continues to exist, but its binding is not included in the lexical information for the module body. If a new variable definition has a counterpart in the old declaration, it effectively assigns to the old variable.

当一个模块声明了一个已被其他模块占用的名字时,新的声明会替换并扩展旧的声明。
如果旧声明中的变量,与新声明中的没有冲突,旧的变量还是会存在,只是它的绑定中并不会包含所处模块中的词法信息。
如果新变量与旧变量冲突了,那么相当于对旧变量重新赋值。

If a module is instantiated in the current namespace’s base phase before the module is redeclared, the redeclaration of the module is immediately instantiated in that phase.

如果模块在当前命名空间的最基础阶段被初始化了,比模块被重新声明更早,
那么重新声明的模块,会立即于那个阶段被初始化。

If the current inspector does not manage a module’s declaration inspector, then the module cannot be redeclared. Similarly, a cross-phase persistent module cannot be redeclared. Even if redeclaration succeeds, instantiation of a module that is previously instantiated may fail if instantiation for the redeclaration attempts to modify variables that are constant (see compile-enforce-module-constants).

如果当前的检测器,无法掌控模块的声明检测器,则这个模块就不能被重新声明。
跨阶段持久化的模块不能被重新声明。

及时重新声明成功了,已经初始化的模块再次初始化,也可能会导致失败,
如果重新声明的模块中,尝试修改了一些常量(例如,compile-enforce-module-constants)。

(5)Submodules

A module or module* form within a top-level module form declares a submodule. A sub-module is accessed relative to its enclosing module, usually with a submod path. Submod-ules can be nested to any depth.

顶层模块中使用 modulemodule* 会声明一个子模块。
子模块可以相对于包含它的模块使用,通常包含一个 submod 路径。
子模块可以嵌套任意深度。

Although a submodule is lexically nested within a module, it cannot necessarily access the bindings of its enclosing module directly. More specifically, a submodule declared with module cannot require from its enclosing module, but the enclosing module can require the submodule. In contrast, a submodule declared with module* conceptually follows its enclosing module, so can require from its enclosing module, but the enclosing module cannot require the submodule. Unless a submodule imports from its enclosing module or vice versa, then visits or instantiations of the two modules are independent, and their implementations may even be loaded from bytecode sources at different times.

尽管子模块是以词法形式嵌套到一个模块中的,但它却不能访问包含它模块内的信息。
更具体的说,通过 module 定义的子模块,无法 require 包含它的模块,但是却可以被包含它的模块 require
通过 module* 定义的子模块,则反之。

除了上述导入方式之外,父子模块的访问和初始化都是独立的,
它们的具体实现,甚至可以字节码的形式被载入不同的次数。

A submodule declared with module can import any preceding submodule declared with module. A submodule declared with module* can import any preceding module declared with module* and any submodule declared with module.

使用 module 声明的子模块,可以引入后面所有使用 module 声明的兄弟子模块。
使用 module* 声明的子模块,可以引入后面所有使用 module*module 声明的兄弟子模块。

When a submodule declaration has the form (module* name #f ....), then all of the bindings of the enclosing module’s bodies are visible in the submodule’s body, and the sub-module implicitly imports the enclosing module. The submodule can provide any bindings that it inherits from its enclosing module.

如果一个子模块是这样声明的,(module* name #f ....)
则父模块中的所有绑定,在子模块中都是可见的,子模块默认导入了父模块。
子模块中还可以提供一些继承于父模块中绑定的绑定。

11. 续延帧和标记(Continuation Frames and Marks)

Every continuation C can be partitioned into continuation frames C_1 , C_2 , ..., C_n such that C = C_1 [C_2 [...[C_n ]]], and no frame C_i can be itself partitioned into smaller continuations. Evaluation steps add frames to and remove frames from the current continuation, typically one at a time.

任何续延(continuation)C 都可以被划分成一系列帧(frame)C_1 , C_2 , ..., C_n
我们有,C = C_1 [C_2 [...[C_n ]]],其中所有的 C_i 都不能再继续划分了。

求值过程中,Racket 会向当前续延(continuation)中添加或删除帧(frame),一步只调整一次。

Each frame is conceptually annotated with a set of continuation marks. A mark consists of a key and its value. The key is an arbitrary value, and each frame includes at most one mark for any given key. Various operations set and extract marks from continuations, so that marks can be used to attach information to a dynamic extent. For example, marks can be used to record information for a “stack trace” to be presented when an exception is raised, or to implement dynamic scope.

理论上每个续延帧会关联到一组续延标记,每个续延标记包含了一个键(key)和它相关的值(value)。
键可以是任意值,每个帧的标记中,相同键的至多有一个。

有很多操作可以设置或获取续延中的标记。
因此,续延标记可被用来结合动态范围(dynamic extent)记录信息,或用来实现动态作用域。

12. prompt,有接续延和屏障(Prompts, Delimited Continuations, and Barriers)

A prompt is a special kind of continuation frame that is annotated with a specific prompt tag (essentially a continuation mark). Various operations allow the capture of frames in the continuation from the redex position out to the nearest enclosing prompt with a particular prompt tag; such a continuation is sometimes called a delimited continuation. Other operations allow the current continuation to be extended with a captured continuation (specifically, a composable continuation). Yet other operations abort the computation to the nearest enclosing prompt with a particular tag, or replace the continuation to the nearest enclosing prompt with another one. When a delimited continuation is captured, the marks associated with the relevant frames are also captured.

prompt 是一类特殊的续延帧,它被打了特殊的标记。
有一些操作,让我们可以捕获从可化简表达式(redex)开始到 prompt 为止的所有续延帧,
这样形成的续延,称为有界续延(delimited continuation)

另外一些操作,允许当前的续延与另外的续延合并(例如,可组合续延(composable continuation))。

还有一些操作可以终止当前的计算,直到 prompt 位置为止。
或者将从当前位置到 prompt 的续延换成另外一个。

当有界续延被捕获时,相应续延帧中的标记也被捕获了。

A continuation barrier is another kind of continuation frame that prohibits certain replacements of the current continuation with another. Specifically, a continuation can be replaced by another only when the replacement does not introduce any continuation barriers. A continuation barrier thus prevents “downward jumps” into a continuation that is protected by a barrier. Certain operations install barriers automatically; in particular, when an exception handler is called, a continuation barrier prohibits the continuation of the handler from capturing the continuation past the exception point.

续延屏障(continuation barrier)是另一类续延帧,它禁止对当前的续延进行替换。
更具体的说,一个续延可以被替换为另一个,仅当替换过程中不涉及任何续延屏障。
续延屏障避免了程序“向下跳转”到被屏障保护的续延中。

一些特定的操作会自动设置续延屏障,
例如,当一个异常处理器被调用的时候,续延屏障保证了,
处理器不能捕获到超出异常位置的续延。

An escape continuation is essentially a derived concept. It combines a prompt for escape purposes with a continuation for mark-gathering purposes. As the name implies, escape continuations are used only to abort to the point of capture.

逃逸续延(escape continuation)是一个衍生概念。
用于直接跳转到捕获的位置。

13. 线程(Threads)

Racket supports multiple threads of evaluation. Threads run concurrently, in the sense that one thread can preempt another without its cooperation, but threads currently all run on the same processor (i.e., the same underlying operating system process and thread).

Racket 在求值时支持多线程。
线程是并发执行的,一个线程会抢占另一个,
但是目前所有的线程都运行在用一个处理器上(类似于底层操作系统的进程和线程)。

Threads are created explicitly by functions such as thread. In terms of the evaluation model, each step in evaluation actually deals with multiple concurrent expressions, up to one per thread, rather than a single expression. The expressions all share the same objects and top-level variables, so that they can communicate through shared state, and sequential consistency is guaranteed (i.e., the result is consistent with some global sequence imposed on all evaluation steps across threads). Most evaluation steps involve a single step in a single expression, but certain synchronization primitives require multiple threads to progress together in one step.

线程是通过调用 thread 创建的。
从求值模型的角度来看,求值的每一步实际上会并发处理多个表达式。

所有的表达式共享相同的对象和顶层变量,所以它们是通过共享状态通信的,保证了顺序的一致性。

大部分求值过程,只对一个表达式求值一步,
但是,一些与线程相关的同步措施,会需要进行多线程处理,然后再整合成一步。

In addition to the state that is shared among all threads, each thread has its own private state that is accessed through thread cells. A thread cell is similar to a normal mutable object, but a change to the value inside a thread cell is seen only when extracting a value from that cell in the same thread. A thread cell can be preserved; when a new thread is created, the creating thread’s value for a preserved thread cell serves as the initial value for the cell in the created thread. For a non-preserved thread cell, a new thread sees the same initial value (specified when the thread cell is created) as all other threads.

除了多线程共享的状态之外,每个线程还有自己的私有状态(可以通过线程单元(thread cell)访问)。
线程单元与普通的可变对象很相似,只是线程单元中的值改变时,只有在同一个线程中对它进行读取时可见。
线程单元可以被保留,当一个新线程创建的时候,父线程的线程单元,可作为子线程线程单元的初始值。

对于未保留的线程单元,新线程与其他线程看到的是相同的初始值。

14. 参数(Parameters)

Parameters are essentially a derived concept in Racket; they are defined in terms of continuation marks and thread cells. However, parameters are also “built in,” due to the fact that some primitive procedures consult parameter values. For example, the default output stream for primitive output operations is specified by a parameter.

参数(Parameters)在 Racket 中是一个衍生概念,它可以从续延标记和线程单元的角度进行定义。
然而,参数也是内置的,因为一些原生函数与参数有关。
例如,原生输出操作中的默认的输出流就被指定了一个参数。

A parameter is a setting that is both thread-specific and continuation-specific. In the empty continuation, each parameter corresponds to a preserved thread cell; a corresponding parameter procedure accesses and sets the thread cell’s value for the current thread.

参数既和线程有关又和续延有关。
对于空的续延,每一个参数关联到了一个被保留的线程单元,参数访问和设置函数可以修改线程单元的值。

In a non-empty continuation, a parameter’s value is determined through a parameterization that is associated with the nearest enclosing continuation frame via a continuation mark (whose key is not directly accessible). A parameterization maps each parameter to a preserved thread cell, and the combination of the thread cell and the current thread yields the parameter’s value. A parameter procedure sets or accesses the relevant thread cell for its parameter.

对于非空续延,参数的值由最近的续延帧决定。
参数表会将每个参数映射到一个被保留的线程单元中,线程调用和当前的线程共同决定了参数的值。
参数访问和设置函数可访问相应的线程单元。

Various operations, such as parameterize or call-with-parameterization, install a parameterization into the current continuation’s frame.

一些操作,例如 parameterizecall-with-parameterization
为当前续延帧设置了参数表。

15. Exceptions

Exceptions are essentially a derived concept in Racket; they are defined in terms of continuations, prompts, and continuation marks. However, exceptions are also “built in,” due to the fact that primitive forms and procedures may raise exceptions.

异常也是一个衍生概念,它是由续延,prompt,以及续延标记定义的。
然而,异常也是内置的,因为一些原生函数可能会触发异常。

An exception handler to catch exceptions can be associated with a continuation frame though a continuation mark (whose key is not directly accessible). When an exception is raised, the current continuation’s marks determine a chain of exception handler procedures that are consulted to handle the exception. A handler for uncaught exceptions is designated through a built-in parameter.

异常处理器,可以通过 catch 表达式和一个续延关联起来。
当一个异常被抛出的时候,当前的续延标记决定了一系列的错误处理函数。
未捕获的异常处理函数,由内置的参数(parameter)指定。

One potential action of an exception handler is to abort the current continuation up to an enclosing prompt with a particular prompt tag. The default handler for uncaught exceptions, in particular, aborts to a particular tag for which a prompt is always present, because the prompt is installed in the outermost frame of the continuation for any new thread.

一个可能的异常处理操作是终止当前的续延,然后跳转到最近的 prompt
对于未捕获异常的默认的处理器,会终止到一个指定的标记处,这里 prompt 总是会提供的。
这个 prompt 被放置在任何新线程的续延帧的最外层。

16. 守卫(Custodians)

A custodian manages a collection of threads, file-stream ports, TCP ports, TCP listeners, UDP sockets, byte converters, and places. Whenever a thread, etc., is created, it is placed under the management of the current custodian as determined by the current-custodian parameter.

守卫(custodian)管理了一组线程,文件流端口,TCP端口,TCP监听器,UDP socket,字节转换器,等等。
任何线程被创建的时候,都会被 current-custodian 参数指定的当前守卫所管控。

Except for the root custodian, every custodian itself is managed by a custodian, so that custodians form a hierarchy. Every object managed by a subordinate custodian is also managed by the custodian’s owner.

除了根守卫之外,每个守卫都被另一个守卫所管理,因此形成了一个等级结构。
每个被一个下属守卫管理的对象,同时也被该守卫的上级所管理。

When a custodian is shut down via custodian-shutdown-all, it forcibly and immediately closes the ports, TCP connections, etc., that it manages, as well as terminating (or suspend-ing) its threads. A custodian that has been shut down cannot manage new objects. After the current custodian is shut down, if a procedure is called that attempts to create a managed resource (e.g., open-input-file, thread), then the exn:fail:contract exception is raised.

当使用 custodian-shutdown-all 关闭一个守卫的时候,会立即关闭它管理的端口,TCP连接,等,
正如结束(或挂起)线程那样。
已经关闭的守卫,将无法管理新的对象。

当前守卫被关闭后,如果一个函数试图创建一个受管控的资源(例如,open-input-filethread),
则会抛出 exn:fail:contract 异常。

A thread can have multiple managing custodians, and a suspended thread created with thread/suspend-to-kill can have zero custodians. Extra custodians become associated with a thread through thread-resume. When a thread has multiple custodians, it is not necessarily killed by a custodian-shutdown-all. Instead, shut-down custodians are removed from the thread’s managing custodian set, and the thread is killed when its managing set becomes empty.

一个线程可以有多个守卫,使用 thread/suspend-to-kill 挂起的线程可以没有守卫。
thread-resume 时,额外的守卫会关联到线程上。

当一个线程有多个守卫时,没必要使用 custodian-shutdown-all 结束进程。
实际上,关闭所有的守卫,会移除线程的所有守卫,而当线程没有守卫时,会被结束掉。

The values managed by a custodian are semi-weakly held by the custodian: a will can be executed for a value that is managed by a custodian; in addition, weak references via weak hash tables, ephemerons, or weak boxes can be dropped on the 3m or CGC variants of Racket, but not on the CS variant. For all variants, a custodian only weakly references its subordinate custodians; if a subordinate custodian is unreferenced but has its own subordinates, then the custodian may be garbage collected, at which point its subordinates become immediately subordinate to the collected custodian’s superordinate (owner) custodian.

被守卫管理的值,并不是完全的弱引用。
守卫可以执行一个 will,除此之外,弱哈希表,ephemerons 或 弱 box 中的弱引用可以被 Racket 变体 3mCGC 中解除,
但是 CS 变体中则不会解除引用。

对于所有的变体而言,守卫只对它的下属是弱引用,如果一个下属守卫不可达,即使它还有自己的下属,
那么也会被垃圾回收。

In addition to the other entities managed by a custodian, a custodian box created with make-custodian-box strongly holds onto a value placed in the box until the box’s custodian is shut down. However, the custodian only weakly retains the box itself, so the box and its content can be collected if there are no other references to them.

除了被守卫管理的其他东西之外,使用 make-custodian-box 创建的守卫 box,
对 box 内的值保存了强引用,除非该 box 的守卫被关闭。

但是守卫对 box 是弱引用,所以 box 连同它的内容,可能被同时垃圾回收。

When Racket is compiled with support for per-custodian memory accounting, the current-memory-use procedure can report a custodian-specific result. This result determines how much memory is occupied by objects that are reachable from the custodian’s managed values, especially its threads, and including its sub-custodians’ managed values. If an object is reachable from two custodians where neither is an ancestor of the other, an object is arbitrarily charged to one or the other, and the choice can change after each collection; objects reachable from both a custodian and its descendant, however, are reliably charged to the custodian and not to the descendants, unless the custodian can reach the objects only through a descendant custodian or a descendant’s thread. Reachability for per-custodian accounting does not include weak references, references to threads managed by other custodians, references to other custodians, or references to custodian boxes for other custodians.

如果 Racket 支持 per-custodian memory accounting 方式进行编译,
current-memory-use 函数可能会返回一个与守卫相关的结果。

结果中包含了守卫管理的对象,线程占用了多大内存,
包括它的下属守卫管理的值。

如果一个对象可以被两个守卫同时访问,每个都不是另外一个祖先,
该对象可能被以任意的方式记录在某一个守卫那里,每次统计都可能会变。

如果对象同事被某个守卫和它的后代管理,
那么核算方式跟守卫有关,而跟它的后裔无关,
除非该守卫只能通过后代守卫,或后代守卫的进程访问这个对象。

per-custodian accounting 的可达性分析,不包括弱引用,
以及被其他守卫管理的线程中的引用,
其他守卫的引用,或者其他守卫的守卫 box里的引用。


参考

The Racket Reference v7.4.0.9

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容