3.3.5 约束的传播

3.3.5 约束的传播
计算机程序传统的组织为单向计算,它执行的计算是在事先指定的实际参数上
生成期望的输出结果。另一个方面,我们常常以数量之间的关系为系统建模。
例如,一个机械学的结构的数学模型可能包括了这样的信息,一个金属杆的
偏斜度d与杆的力量F,杆的长度L,它的横截面积A,弹性系数E 有关,方程如下:

dAE=FL

这样的一个方程不是一个方向的。给定任意的四个变量,我们能使用它计算第五个变量。
然而翻译这个方程为一个传统的计算机语言时将强迫我们选择变量中的一个作为其它的
四个变量的计算结果。因此一个计算A的程序不能被用来计算d,即使它们都引用了相同
的方程也不行。

在这部分中,我们刻画了一种语言的设计,为了让我们能够工作在关系本身上。
语言的原生的元素是原生的约束,它表明变量之间存在的特定的关系。例如,
(adder a b c)指定了变量 a,b,c必须满足方程关系 a+b=c, (multiplier x y z)表达了约束
x*y=z 还有(constant 3.14 x)说是的 x的值必须是3.14

为了表达更加复杂的关系,我们的语言提供了一个组合原生的约束的方法。 我们通过组装约束的网络
来组合约束,用连接器把约束连接起来。一个连接器是一个能够存储一个值的对象,这个值可能
在一个或者多个约束中使用。例如我们知道温度的华氏度与摄氏度之间存在如下的关系:

9*C=5*(F-32)

这样的约束能被认为是一个网络,这个网络由原生的加法器,乘法器和常数的约束组成。
(见图3.28). 在图中,我们看到了在左边的乘法器的盒子有三个终端,标签为m1,m2,p。
它们连接了乘法器和网络的其它部分如下: 终端m1被连接到一个连接器C,这个连接器
存储摄氏温度。终端m2被连接到一个连接器w,w又连接到一个存储值为9的常数盒子。
终端p被乘法器约束着,成为了m1和m2的积,这个终端p被连接到了另一个乘法器的终端p上。
而对于另一个乘法器而言,它的m2被连接到常数5,它的m1被连接到加法器的一个加数上。

图3.28  关系 9*C=5*(F-32)表达为一个约束的网络

这样的网络执行的计算如下:当一个连接器被给定一个值(可能是用户给定的
也可能是由它连接的约束例子给定的)它唤醒与它自己相关的约束(除了唤醒它的约束)
通知它们,它有一个值了。任何一个被唤醒的约束盒子把它的连接器取过来,看一看是否
有足够的信息为一个连接器确定一个值。如果是,盒子设置连接器,它再唤醒与它相关的约束盒子,等等。
例如,在摄氏度与华氏度的例子中,w,x,y都是立即被相应地设置为9,5,32。
连接器唤醒了乘法器和加法器,它确定没有足够的信息用来计算。如果用户(或者是网络的一部分)
设置c为一个值(例如25)最左边的乘法器被唤醒,它将设置u为25*9=225。然后u唤醒第二个乘法器,
它设置v为45,v唤醒加法器,它设置F为77。


* 使用约束系统
为了使用约束系统来执行如上的温度计算,我们首先创建两个连接器,
C和F,通过调用组装子make-connector,以合适的网络来连接C和F:

(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))

这个程序创建了内部的连接器,u,v,w,x,y,使用原生的约束组装子,
adder,multiplier和constant,如图3.28中的那样,连接它们。
正如3.3.4部分中的数字电路模拟器那样,表达原生的元素的组合,使用了
我们的语言中程序自动地提供了为复合对象的抽象的方法。

为了观察网络的动向,我们能在连接器c和f上设置探针。使用一个探针程序与
我们在3.3.4部分中的监控线路的所使用的那个程序相似。在一个连接器上设置
探针,当连接器被给定一个值时,能引起一个消息被打印出来:

(probe "Celsius temp" C)
(probe "Fahrenheit" F)

接下来,我们设置C的值为25(set-value!的第三个实际参数告诉C这个推导来自于用户)

(set-value! C 25 'user)
Probe: Celsius temp=25
Probe: Fahrenheit temp = 77
done

在C上的探针被唤醒与报告了值。正如上述的描述C也把它的值在网络中传播了。
这设置F为77,这是被F上的探针报告的。

现在我们能试图把F设置一个新的值,如212:

(set-value! F 212 'user)
错误! 矛盾(77 212)

连接器抱怨它发现了一个矛盾:它的值是77,有人要设置为212。如果我们真要以新的值重用网络,
我们能告诉C忘掉它的旧值:

(forget-value! C 'user)
Probe: Celsius temp = ?
Probe: Fahrenheit temp = ?
done

C发现了用户设置了旧值,现在正撤回这个值,所以C同意丢掉它的值,
正如探针所示的那样,并且把这个事实通知了网络的其它部分。这个信息
最终传给了F,它现在发现没有理由继续认为它的值是77。所以F也放弃了
它的值,正如探针所示。

现在F没有值,我们能自由地把它设置成212:

(set-value! F 212 'user)
Probe: Fahrenheit temp = 212
Probe: Celsius temp = 100
done

这个新值,当它通过网络传播时,它强迫C等于100。

(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
)

Adder这个加法器把新的加法器连接到了指定的连接器上,返回它的值。
程序me,它表示加法器,它的行为是一个内部的程序的分发器。如下的
语法接口被使用在与分发的关联:

(define (inform-about-value constraint)
 (constraint 'I-have-a-value))
(define (inform-about-no-value constratint)
(constraint 'I-lost-my-value))

在加法器的连接器之一有了值,这个加法器被通知时,加法器的内部程序process-new-value
被调用。加法器首先检查a1和a2是否都有值,如果是,它告诉sum设置为两个加数的和。
对于set-value!的informant参数是me,它是加法器本身。如果a1和a2不是都有值,那么,加法器检查
可能是a1和sum有值,如果是,设置a2为这两个值的差。最后,如果是a2和sum有值,这有足够的信息,
让加法器设置a1.如果加法器被告诉它的连接器之一已经忘记了它的值,它要求所有的连接器都忘记
它们自己的值。(仅仅是被这个加法器设置的值会实际上丢掉。)然后它运行process-new-value.
这最后一步的理由是一个或者是多个连接器可能仍然有一个值(即一个连接器可能已经有一个值
不是被加法器初始设置的)这些值需要通过加法器收回。

一个乘法器与一个加法器是相似的。它将设置一个product为0,如果任何一个乘数为0,即使其它的还不知道。

(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? m1) (has-value? product))
            (set-value? m2 (/ (get-value product) (get-value m1)) me))
          ((and (has-value? a2) (has-value? product))
           (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 CONSTAT" 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
)

* 表示 连接器
一个连接器被表示为一个有局部状态变量的程序化对象,value是连接器的当前值;
informant是设置连接器值的对象,constraints是连接器参与的约束的列表。

(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 flase)
                       (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 CONNNECTOR" request))      
          )
      )
      me
  )
)

当有一个设置连接器的值的请求时,连接器的内部程序set-my-value被调用。如果连接器在当前没有一个值,
它将被设置并且被记录在informant,然后连接器通知相关的约束要设置值除了要求它设置值的约束。这能
使用如下的迭代器完成任务。它应用一个特定的程序到列表中除了给定的元素的其它元素上:

(define (for-each-except exception procedure list)
    (define (loop items)
        (cond ((null? items) 'done)
              ((eq? (car items) exception) (loop (cdr items)))
              (else (procedure (cdr items))
                    (loop (cdr items))))
    )
    (loop list)
)

如果连接器被要求忘记它的值,它运行内部程序forget-my-value,它首先检查确保请求来自于
初始设置这个值的对象。如果是连接器通知相关的约束丢掉它的值。

内部程序connect加上特定的约束,如果这个约束不在约束的列表中,就加到约束的列表中。
然后,如果连接器有一个值,它把这个事实,通知新约束。

连接器的程序me作为一个对于内部其它的程序的分发器也表示连接器为一个对象。如下的程序
为分发器提供了一个语法上的接口:

(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))


练习3.33
使用原生的加法,乘法,和常数约束,定义一个程序averager,它有三个实际参数,
a,b,c为输入,建立约束即c是a,b的平均数。

练习3.34
Louis想到构建了一个平方的约束,一个约束设备两个终端,例如,连接器的值b,
第二个终端总是值a的平方。他认为如下的简单的设备由一个乘法器组成:

(define (squarer a b)
   (multiplier a a b)
)

在这个想法中,这有一个严重的缺陷,解释一下:

练习3.35
Ben告诉Louis有一个方式可以避免练习3.34中的问题,定义一个新的squarer作为一个新的原生的约束,
在Ben的程序中,为了实现这样的约束,请填写空白处的内部:

(define (squarer a b)
   (define (process-new-value)
      (if (has-value? b)
           (if (< (get-value b) 0)
               (error "square less than 0 ---SQUARER" (get-value b))
               <alternative1> )
          <alternative2>
      )
   )
   (define (process-forget-value) <body1>)
   (define (me request) <body2>)
    <rest-of definition>
    me
)


练习3.36
假定在全局的环境中,我们要解释如下的表达式:

(define a (make-connector))
(define b (make-connector))
(set-value! a 10 'user)

在解释set-value!的过程中的某一个时刻,如下的表达式在连接器的内部程序正在被解释:

(for-each-except setter inform-about-value constraints)

画出如上的表达式解释时,在一个环境的图中的环境的情况。

练习3.37
当有一个更面向表达式的风格的定义时,celsius-fahrenheit-converter程序有一些奇怪了。
例如:

(define (celsius-fahrenheit-converter x)
   (c+ (c* (c/ (cv 9) (cv 5))
           x)
        (cv 32)))
(define  C (make-connector))
(define F (celsius-fahrenheit-converter C))

这里的c+,c*等,是算术操作的约束的版本。例如 c+有两个连接器作为参数,
返回一个连接器,它是这两个连接器的相关的加法的约束:

(define (c+ x y)
   (let ((z (make-connector)))
        (adder x y z)
  z)
)

定义相似的程序c-,c*,c/和 cv(常数值)让我们能定义复合的约束,正如上述的反转器。

猜你喜欢

转载自blog.csdn.net/gggwfn1982/article/details/81627609
今日推荐