Computer programs are traditionally organized as one-directional computations,
which perform operations on prespecified arguments to produce desired outputs.
On the other hand, we often model systems in terms of relations among
quantities. For example, a mathematical model of a mechanical structure might
include the information that the deflection d of a metal rod is related to
the force F on the rod, the length L of the rod, the cross-sectional
area A, and the elastic modulus E via the equation
d
A
E
计算机程序传统上被组织为单向计算——对预先指定的参数执行运算以产生所需的输出。而另一方面,我们常常用量与量之间的关系来对系统建模。例如,一根金属杆的力学模型可能包含这样的信息:杆的挠度 d 与杆所受的力 F、杆的长度 L、截面积 A 以及弹性模量 E 之间,通过以下方程相关联:d A E
=
F
L
.
Such an equation is not one-directional. Given any four of the quantities, we
can use it to compute the fifth. Yet translating the equation into a
traditional computer language would force us to choose one of the quantities to
be computed in terms of the other four. Thus, a procedure for computing the
area A could not be used to compute the deflection d, even though the
computations of A and d arise from the same
equation.
F L。这样的方程并不是单向的。给定其中任意四个量,都可以用它来计算第五个量。然而,若将该方程翻译成传统计算机语言,则会迫使我们选择某一个量作为由其余四个量计算出来的结果。因此,一个用于计算截面积 A 的过程,便无法用于计算挠度 d,尽管 A 与 d 的计算源于同一个方程。
In this section, we sketch the design of a language that enables us to work in
terms of relations themselves. The primitive elements of the language are
本节将勾勒一种语言的设计,使我们能够直接在关系本身的层面上工作。该语言的基本元素是
primitive constraints, which state that certain relations hold
between quantities. For example, (adder a b c) specifies that the
quantities a, b, and c must be related by the equation
a
+
b
=
c, (multiplier x y z) expresses the constraint
x
y
=
z, and (constant 3.14 x) says that the value of x must be 3.14.
基本约束 (primitive constraints),它们声明量与量之间必须满足的特定关系。例如,`(adder a b c)` 指定量 a、b 和 c 必须满足方程 a + b = c,`(multiplier x y z)` 表达约束 x y = z,而 `(constant 3.14 x)` 则说明 x 的值必须为 3.14。
Our language provides a means of combining primitive constraints in order to
express more complex relations. We combine constraints by constructing
我们的语言提供了一种组合基本约束的方法,以表达更复杂的关系。我们通过构造
constraint networks, in which constraints are joined by
约束网络 (constraint networks) 来组合约束,其中各约束通过
connectors. A connector is an object that “holds” a value that may
participate in one or more constraints. For example, we know that the
relationship between Fahrenheit and Celsius temperatures is
9
C
连接器 (connectors) 相互连接。连接器是一种"持有"某个值的对象,该值可以参与一个或多个约束。例如,我们知道华氏温度与摄氏温度之间的关系为 9 C
=
5
(
F
−
32
)
.
Such a constraint can be thought of as a network consisting of primitive adder,
multiplier, and constant constraints (Figure 3.28). In the figure, we
see on the left a multiplier box with three terminals, labeled m
1, m
2,
and p. These connect the multiplier to the rest of the network as follows:
The m
1 terminal is linked to a connector C, which will hold the Celsius
temperature. The m
2 terminal is linked to a connector w, which is also
linked to a constant box that holds 9. The p terminal, which the
multiplier box constrains to be the product of m
1 and m
2, is linked to
the p terminal of another multiplier box, whose m
2 is connected to a
constant 5 and whose m
1 is connected to one of the terms in a sum.
5 ( F − 32 )。这样的约束可以看作由基本加法器、乘法器和常量约束构成的网络(图 3.28)。在图中,左侧是一个带有三个端口的乘法器方框,标记为 m1、m2 和 p。它们将该乘法器与网络其余部分连接如下:m1 端口与连接器 C 相连,C 将持有摄氏温度;m2 端口与连接器 w 相连,w 同时连接到一个持有常量 9 的常量方框;p 端口(乘法器约束其值等于 m1 与 m2 之积)连接到另一个乘法器方框的 p 端口,后者的 m2 连接到常量 5,其 m1 连接到一个加法运算的某一项。
Figure 3.28: The relation 9
C
=
5
(
F
−
32
) expressed as a constraint network.
图 3.28:以约束网络表达的关系 9 C = 5 ( F − 32 )。
Computation by such a network proceeds as follows: When a connector is given a
value (by the user or by a constraint box to which it is linked), it awakens
all of its associated constraints (except for the constraint that just awakened
it) to inform them that it has a value. Each awakened constraint box then
polls its connectors to see if there is enough information to determine a value
for a connector. If so, the box sets that connector, which then awakens all of
its associated constraints, and so on. For instance, in conversion between
Celsius and Fahrenheit, w, x, and y are immediately set by the
constant boxes to 9, 5, and 32, respectively. The connectors awaken the
multipliers and the adder, which determine that there is not enough information
to proceed. If the user (or some other part of the network) sets C to a
value (say 25), the leftmost multiplier will be awakened, and it will set u
to 25
⋅
9
=
225. Then u awakens the second multiplier, which sets v to
45, and v awakens the adder, which sets F to 77.
Subheading: Using the constraint system
这种网络的计算过程如下:当某个连接器被赋值时(由用户或与之相连的约束方框赋值),它会唤醒与其关联的所有约束(除了刚刚唤醒它的那个约束),通知它们自己已有了值。每个被唤醒的约束方框随即轮询其各个连接器,看是否有足够的信息来确定某个连接器的值。如果有,该方框便设置那个连接器,后者再唤醒与其关联的所有约束,如此往复。例如,在摄氏与华氏的换算中,w、x 和 y 被各自的常量方框立即设为 9、5 和 32。这些连接器唤醒各乘法器和加法器,而它们判断目前信息不足,无法继续推进。若用户(或网络的其他部分)将 C 设为某个值(例如 25),最左侧的乘法器将被唤醒,并把 u 设为 25 ⋅ 9 = 225。然后 u 唤醒第二个乘法器,将 v 设为 45,v 再唤醒加法器,将 F 设为 77。子标题:使用约束系统
To use the constraint system to carry out the temperature computation outlined
above, we first create two connectors, C and F, by calling the
constructor make-connector, and link C and F in an
appropriate network:
为了使用约束系统来完成上述温度换算,我们首先调用构造函数 make-connector 创建两个连接器 C 和 F,并将它们连接到一个适当的网络中:
The procedure that creates the network is defined as follows:
创建该网络的过程定义如下:
This procedure creates the internal connectors u, v, w,
x, and y, and links them as shown in Figure 3.28 using the
primitive constraint constructors adder, multiplier, and
constant. Just as with the digital-circuit simulator of
3.3.4, expressing these combinations of primitive elements in terms of
procedures automatically provides our language with a means of abstraction for
compound objects.
此过程创建内部连接器 u、v、w、x 和 y,并按图 3.28 所示,使用基本约束构造函数 adder、multiplier 和 constant 将它们连接起来。正如 3.3.4 节中数字电路模拟器的情形一样,以过程的形式表达这些基本元素的组合,自然地为我们的语言提供了一种对复合对象进行抽象的方法。
To watch the network in action, we can place probes on the connectors C
and F, using a probe procedure similar to the one we used to
monitor wires in 3.3.4. Placing a probe on a connector will
cause a message to be printed whenever the connector is given a value:
为了观察网络的实际运作,我们可以在连接器 C 和 F 上设置探针,使用一个与 3.3.4 节中监控导线类似的 probe 过程。在连接器上设置探针后,每当连接器被赋值,都会打印出一条消息:
Next we set the value of C to 25. (The third argument to
set-value! tells C that this directive comes from the
user.)
接下来我们把 C 的值设为 25。(`set-value!` 的第三个参数告知 C,此指令来自用户。)
The probe on C awakens and reports the value. C also propagates
its value through the network as described above. This sets F to 77,
which is reported by the probe on F.
Now we can try to set F to a new value, say 212:
C 上的探针被唤醒并报告该值。C 同时按上述描述将其值通过网络传播,这将 F 设为 77,并由 F 上的探针报告出来。现在我们尝试将 F 设为一个新值,例如 212:
The connector complains that it has sensed a contradiction: Its value is 77,
and someone is trying to set it to 212. If we really want to reuse the network
with new values, we can tell C to forget its old value:
连接器抱怨它感知到了矛盾:它的值是 77,而有人试图将其设为 212。如果我们真的想用新值重新使用这个网络,可以告知 C 忘掉它的旧值:
C finds that the user, who set its value originally, is now
retracting that value, so C agrees to lose its value, as shown by the
probe, and informs the rest of the network of this fact. This information
eventually propagates to F, which now finds that it has no reason for
continuing to believe that its own value is 77. Thus, F also gives up
its value, as shown by the probe.
Now that F has no value, we are free to set it to 212:
C 发现原先设置其值的用户现在正在撤回该值,因此 C 同意丢弃自己的值(如探针所示),并将此事实通知网络其余部分。这一信息最终传播到 F,F 发现自己再也没有理由相信其值为 77。于是 F 也放弃了自己的值(如探针所示)。现在 F 已没有值,我们便可以自由地将其设为 212:
This new value, when propagated through the network, forces C to have a
value of 100, and this is registered by the probe on C. Notice that the
very same network is being used to compute C given F and to
compute F given C. This nondirectionality of computation is the
distinguishing feature of constraint-based systems.
Subheading: Implementing the constraint system
这个新值经网络传播后,迫使 C 取得值 100,这由 C 上的探针记录下来。注意,正是同一个网络,既可以在给定 F 的情况下计算 C,也可以在给定 C 的情况下计算 F。计算的这种非方向性,正是基于约束的系统的显著特征。子标题:实现约束系统
The constraint system is implemented via procedural objects with local state,
in a manner very similar to the digital-circuit simulator of
3.3.4. Although the primitive objects of the constraint system are
somewhat more complex, the overall system is simpler, since there is no concern
about agendas and logic delays.
The basic operations on connectors are the following:
约束系统通过带有局部状态的过程性对象来实现,其方式与 3.3.4 节中的数字电路模拟器非常相似。尽管约束系统的基本对象稍微复杂一些,但整个系统反而更简单,因为无需考虑议程和逻辑延迟的问题。连接器上的基本操作如下:
(has-value? ⟨connector⟩) tells whether the connector has a value.
`(has-value? ⟨connector⟩)` 判断连接器是否持有一个值。
(get-value ⟨connector⟩) returns the connector’s current value.
`(get-value ⟨connector⟩)` 返回连接器当前的值。
(set-value! ⟨connector⟩ ⟨new-value⟩ ⟨informant⟩)
indicates that the informant is requesting the connector to set its value to
the new value.
`(set-value! ⟨connector⟩ ⟨new-value⟩ ⟨informant⟩)` 指示通报者 (informant) 请求连接器将其值设为新值。
(forget-value! ⟨connector⟩ ⟨retractor⟩) tells the connector
that the retractor is requesting it to forget its value.
`(forget-value! ⟨connector⟩ ⟨retractor⟩)` 告知连接器,撤回者 (retractor) 请求它忘掉自己的值。
(connect ⟨connector⟩ ⟨new-constraint⟩) tells the connector
to participate in the new constraint.
`(connect ⟨connector⟩ ⟨new-constraint⟩)` 告知连接器参与新的约束。
The connectors communicate with the constraints by means of the procedures
inform-about-value, which tells the given constraint that the connector
has a value, and inform-about-no-value, which tells the constraint that
the connector has lost its value.
连接器通过过程 inform-about-value 和 inform-about-no-value 与约束进行通信:前者告知给定约束该连接器已有值,后者告知约束该连接器已失去其值。
Adder constructs an adder constraint among summand connectors a1
and a2 and a sum connector. An adder is implemented as a
procedure with local state (the procedure me below):
Adder 在被加数连接器 a1 和 a2 以及和连接器 sum 之间构造一个加法约束。加法器以带有局部状态的过程形式实现(即下面的过程 me):
Adder connects the new adder to the designated connectors and returns it
as its value. The procedure me, which represents the adder, acts as a
dispatch to the local procedures. The following “syntax interfaces” (see
Footnote 155 in 3.3.4) are used in conjunction with
the dispatch:
Adder 将新建的加法器连接到指定的连接器,并将其作为返回值返回。代表加法器的过程 me 充当对各内部过程的分派。下列"语法接口"(参见 3.3.4 节脚注 155)配合分派使用:
The adder’s local procedure process-new-value is called when the adder
is informed that one of its connectors has a value. The adder first checks to
see if both a1 and a2 have values. If so, it tells sum to
set its value to the sum of the two addends. The informant argument to
set-value! is me, which is the adder object itself. If a1
and a2 do not both have values, then the adder checks to see if perhaps
a1 and sum have values. If so, it sets a2 to the
difference of these two. Finally, if a2 and sum have values,
this gives the adder enough information to set a1. If the adder is told
that one of its connectors has lost a value, it requests that all of its
connectors now lose their values. (Only those values that were set by this
adder are actually lost.) Then it runs process-new-value. The reason
for this last step is that one or more connectors may still have a value (that
is, a connector may have had a value that was not originally set by the adder),
and these values may need to be propagated back through the adder.
加法器的局部过程 process-new-value 在加法器被告知其某个连接器已有值时被调用。加法器首先检查 a1 和 a2 是否都有值;若是,则通知 sum 将其值设为两个被加数之和,传给 `set-value!` 的通报者参数是 me,即加法器对象本身。若 a1 和 a2 并非都有值,加法器则检查 a1 和 sum 是否都有值;若是,就将 a2 设为二者之差。最后,若 a2 和 sum 都有值,加法器便有足够信息来设定 a1。若加法器被告知其某个连接器失去了值,则请求所有连接器放弃各自的值(实际上只有由该加法器设定的值才会被放弃),然后再运行 process-new-value。最后这一步的原因在于:一个或多个连接器可能仍持有值(即某连接器的值并非最初由该加法器设定),这些值可能需要反向经由加法器传播。
A multiplier is very similar to an adder. It will set its product to 0
if either of the factors is 0, even if the other factor is not known.
乘法器与加法器非常相似。如果某个因子为 0,即使另一个因子未知,乘法器也会将其积设为 0。
A constant constructor simply sets the value of the designated
connector. Any I-have-a-value or I-lost-my-value message sent to
the constant box will produce an error.
常量构造函数只是简单地设定指定连接器的值。发送给常量方框的任何"我有值"或"我失去值"消息都会产生错误。
Finally, a probe prints a message about the setting or unsetting of
the designated connector:
最后,探针 (probe) 在指定连接器被设值或取消设值时打印一条消息:
Subheading: Representing connectors
子标题:表示连接器
A connector is represented as a procedural object with local state variables
value, the current value of the connector; informant, the object
that set the connector’s value; and constraints, a list of the
constraints in which the connector participates.
连接器用带有局部状态变量的过程性对象来表示:value 是连接器的当前值;informant 是设置该连接器值的对象;constraints 是连接器所参与的全部约束构成的表。
The connector’s local procedure set-my-value is called when there is a
request to set the connector’s value. If the connector does not currently have
a value, it will set its value and remember as informant the constraint
that requested the value to be set. Then the connector will notify all of its participating
constraints except the constraint that requested the value to be set. This is
accomplished using the following iterator, which applies a designated procedure
to all items in a list except a given one:
连接器的局部过程 set-my-value 在收到设置连接器值的请求时被调用。若连接器当前没有值,它将设定自己的值,并将请求设值的约束记录为 informant;随后,连接器通知其参与的所有约束,唯独不通知那个提出设值请求的约束。这通过如下迭代器来完成——它对表中除某个给定元素外的所有元素应用指定过程:
If a connector is asked to forget its value, it runs the local procedure
forget-my-value, which first checks to make sure that the request is
coming from the same object that set the value originally. If so, the
connector informs its associated constraints about the loss of the value.
若连接器被要求忘掉自己的值,它会运行局部过程 forget-my-value,该过程首先检查请求是否来自最初设定该值的同一对象;若是,连接器便将值的丢失告知其所有关联约束。
The local procedure connect adds the designated new constraint to the
list of constraints if it is not already in that list. Then, if the connector
has a value, it informs the new constraint of this fact.
局部过程 connect 将指定的新约束加入约束表(如果尚未在其中)。然后,若连接器已有值,便将这一事实告知新约束。
The connector’s procedure me serves as a dispatch to the other internal
procedures and also represents the connector as an object. The following
procedures provide a syntax interface for the dispatch:
连接器的过程 me 充当对其他内部过程的分派,同时也将连接器作为对象加以表示。以下过程为分派提供语法接口:
(define C (make-connector))
(define F (make-connector))
(celsius-fahrenheit-converter C F)
ok (define (celsius-fahrenheit-converter c f)
(let ((u (make-connector))
(v (make-connector))
(w (make-connector))
(x (make-connector))
(y (make-connector)))
(multiplier c w u)
(multiplier v x u)
(adder v y f)
(constant 9 w)
(constant 5 x)
(constant 32 y)
'ok)) (probe "Celsius temp" C)
(probe "Fahrenheit temp" F) (set-value! C 25 'user)
Probe: Celsius temp = 25
Probe: Fahrenheit temp = 77
done (set-value! F 212 'user)
Error! Contradiction (77 212) (forget-value! C 'user)
Probe: Celsius temp = ?
Probe: Fahrenheit temp = ?
done (set-value! F 212 'user)
Probe: Fahrenheit temp = 212
Probe: Celsius temp = 100
done (define (adder a1 a2 sum)
(define (process-new-value)
(cond ((and (has-value? a1)
(has-value? a2))
(set-value! sum
(+ (get-value a1)
(get-value a2))
me))
((and (has-value? a1)
(has-value? sum))
(set-value! a2
(- (get-value sum)
(get-value a1))
me))
((and (has-value? a2)
(has-value? sum))
(set-value! a1
(- (get-value sum)
(get-value a2))
me))))
(define (process-forget-value)
(forget-value! sum me)
(forget-value! a1 me)
(forget-value! a2 me)
(process-new-value))
(define (me request)
(cond ((eq? request 'I-have-a-value)
(process-new-value))
((eq? request 'I-lost-my-value)
(process-forget-value))
(else (error "Unknown request:
ADDER" request))))
(connect a1 me)
(connect a2 me)
(connect sum me)
me) (define (inform-about-value constraint)
(constraint 'I-have-a-value))
(define (inform-about-no-value constraint)
(constraint 'I-lost-my-value)) (define (multiplier m1 m2 product)
(define (process-new-value)
(cond ((or (and (has-value? m1)
(= (get-value m1) 0))
(and (has-value? m2)
(= (get-value m2) 0)))
(set-value! product 0 me))
((and (has-value? m1)
(has-value? m2))
(set-value! product
(* (get-value m1)
(get-value m2))
me))
((and (has-value? product)
(has-value? m1))
(set-value! m2
(/ (get-value product)
(get-value m1))
me))
((and (has-value? product)
(has-value? m2))
(set-value! m1
(/ (get-value product)
(get-value m2))
me))))
(define (process-forget-value)
(forget-value! product me)
(forget-value! m1 me)
(forget-value! m2 me)
(process-new-value))
(define (me request)
(cond ((eq? request 'I-have-a-value)
(process-new-value))
((eq? request 'I-lost-my-value)
(process-forget-value))
(else
(error "Unknown request:
MULTIPLIER"
request))))
(connect m1 me)
(connect m2 me)
(connect product me)
me) (define (constant value connector)
(define (me request)
(error "Unknown request: CONSTANT"
request))
(connect connector me)
(set-value! connector value me)
me) (define (probe name connector)
(define (print-probe value)
(newline) (display "Probe: ")
(display name) (display " = ")
(display value))
(define (process-new-value)
(print-probe (get-value connector)))
(define (process-forget-value)
(print-probe "?"))
(define (me request)
(cond ((eq? request 'I-have-a-value)
(process-new-value))
((eq? request 'I-lost-my-value)
(process-forget-value))
(else (error "Unknown request:
PROBE" request))))
(connect connector me)
me) (define (make-connector)
(let ((value false)
(informant false)
(constraints '()))
(define (set-my-value newval setter)
(cond ((not (has-value? me))
(set! value newval)
(set! informant setter)
(for-each-except
setter
inform-about-value
constraints))
((not (= value newval))
(error "Contradiction"
(list value newval)))
(else 'ignored)))
(define (forget-my-value retractor)
(if (eq? retractor informant)
(begin (set! informant false)
(for-each-except
retractor
inform-about-no-value
constraints))
'ignored))
(define (connect new-constraint)
(if (not (memq new-constraint
constraints))
(set! constraints
(cons new-constraint
constraints)))
(if (has-value? me)
(inform-about-value new-constraint))
'done)
(define (me request)
(cond ((eq? request 'has-value?)
(if informant true false))
((eq? request 'value) value)
((eq? request 'set-value!)
set-my-value)
((eq? request 'forget)
forget-my-value)
((eq? request 'connect) connect)
(else (error "Unknown operation:
CONNECTOR"
request))))
me)) (define (for-each-except exception
procedure
list)
(define (loop items)
(cond ((null? items) 'done)
((eq? (car items) exception)
(loop (cdr items)))
(else (procedure (car items))
(loop (cdr items)))))
(loop list)) (define (has-value? connector)
(connector 'has-value?))
(define (get-value connector)
(connector 'value))
(define (set-value! connector
new-value
informant)
((connector 'set-value!)
new-value
informant))
(define (forget-value! connector retractor)
((connector 'forget) retractor))
(define (connect connector new-constraint)
((connector 'connect) new-constraint))