了解评估的环境模型

时间:2018-02-17 15:19:48

标签: scheme evaluation sicp lexical-scope

在SICP练习3.20:

  

绘制环境图以说明序列的评估   表达式

(define x (cons 1 2))
(define z (cons x x))


(set-car! (cdr z) 17)

(car x) 17
     

使用上面给出的对的程序实现。

我的眼睛被摧毁,所以我无法画画。相反,我会尝试尽可能地想象环境模型如何演变。

首先,这是程序对实现。

(define (cons x y)
  (define (set-x! v) (set! x v))
  (define (set-y! v) (set! y v))
  (define (dispatch m)
    (cond ((eq? m 'car) x)
          ((eq? m 'cdr) y)
          ((eq? m 'set-car!) set-x!)
          ((eq? m 'set-cdr!) set-y!)
          (else (error "Undefined 
                 operation: CONS" m))))
  dispatch)

(define (car z) (z 'car))
(define (cdr z) (z 'cdr))

(define (set-car! z new-value)
  ((z 'set-car!) new-value)
  z)

(define (set-cdr! z new-value)
  ((z 'set-cdr!) new-value)
  z)

每个都有指向全球环境的指针。

这是我想象中的互动和解决方案。

(define x (cons 1 2))

申请利弊 cons创造了一个叫做e1的环境 - 全球环境就是封闭的环境 x绑定到1
y必然是2
set-x!,set-y!并且每个都有一个指向e1的指针 dispatch绑定到全局环境中的名称x

(define z (cons x x))

申请利弊 缺点创造e2 - 全球是封闭的 x关于全局(共享)的x绑定到x y关于全局(共享)的x绑定到x set-x!,set-y!并且每个都有一个指向e2的指针 dispatch绑定到全局环境中的名称z

(set-car! (cdr z) 17)

应用set-car!
设置的车!创造e3 - 全球是封闭的 对于全局
,z被绑定到(cdr z) 申请cdr
cdr创建e4 - 全局封闭
关于全局
,z与z绑定 调度创建e5 - e2封闭
m必然是' cdr
相对于e2返回x x与全局共享x(以及与e1相关的x)
回到e3
新价值必然是17
调度创建e6 - e2封闭
m必然会设置“车”! 设置-X!关于e2返回
申请set-x!
设置-X!创造e7 - e2是封闭的 新价值必然是17
设定x相对于e2到17
由于x是e1,e2中的x和全局中的x共享一个过程对象,该过程对象具有指向e1的指针,因此过程对象的汽车被更改。

我希望这是可以理解的。我的解决方案是否正确?

1 个答案:

答案 0 :(得分:3)

以下是您具体问题的答案。

我将全局变量x重命名为v以避免混淆。

 (define v (cons 1 2))
     

申请利弊   cons创造了一个叫做e1的环境 - 全球环境就是封闭的环境   x绑定到1
  y必然是2
  set-x!,set-y!并且每个都有一个指向e1的指针   dispatch绑定到全局环境中的名称v

正确。

 (define z (cons v v))
     

申请利弊   利弊创造了e2 - 全球正在封闭中   对于全局(共享),x绑定到v   对于全局(共享),y必然是v   set-x!,set-y!并且每个都有一个指向e2的指针   dispatch绑定到全局环境中的名称z

正确。

 (set-car! (cdr z) 17)
     

应用set-car!
  设置的车!创造e3 - 全球围绕着

没有。

(在下文中,不使用代码格式标记,以使视障人士的噪音最小化。)

首先,评估(cdr z)。它相当于(z' cdr)。 z必然会从e2帧调度。收到消息&#cd; cdr后,这将发送给e2&#y; y。此访问 e2环境框架中的y槽,它保存来自全局环境的v值。

接下来,评估(set-car!v 17)。它相当于((v' set-car!)17)。 v必须发送e1帧的 。收到了这套车!消息,它发送到e1的set-x!功能。因此,这将调用(set-x!17)e1的set-x!。这又在环境框架e1内调用(set!x 17)。因此它访问 - 并且修改 - " x"环境框架e1中的插槽!

从现在开始,v的任何未来操作都将反映此更改,因为v指的是框架e1,并且该框架已已更改。 e1帧的存储值在" x"不再是1,而是17。

访问这些值不会创建新的环境框架。这些值引用的隐藏帧被访问,并可能被修改。

只有cons会创建新的隐藏环境框架,这些框架会附加到新创建的" cons"值(即调度函数)。

首先编写以下内容,作为说明。不幸的是,我怀疑它对视觉(如果有的话)更有帮助。它包括逐步评估过程。

我将首先重新编写您的cons函数,作为等效的,只是更冗长的

(define cons 
   (lambda (x y)
     (letrec ([set-x! (lambda (v) (set! x v))]
              [set-y! (lambda (v) (set! y v))]
              [dispatch 
                  (lambda (m)
                    (cond ((eq? m 'car) x)
                          ((eq? m 'cdr) y)
                          ((eq? m 'set-car!) set-x!)
                          ((eq? m 'set-cdr!) set-y!)
                          (else (error 
                            "CONS: ERROR: unrecognized op name" m))))])
        dispatch)))

更多地强调它的方面,lambda函数也是值,它们可以被创建,命名和返回。现在,上述意味着在我们的代码中编写(cons 1 2)与编写

相同
(let ([x 1]
      [y 2])
     (letrec ; EXACTLY the same code as above
             ([set-x! (lambda (v) (set! x v))]
              [set-y! (lambda (v) (set! y v))]
              [dispatch 
                  (lambda (m)
                    (cond ((eq? m 'car) x)
                          ((eq? m 'cdr) y)
                          ((eq? m 'set-car!) set-x!)
                          ((eq? m 'set-cdr!) set-y!)
                          (else (error 
                            "CONS: ERROR: unrecognized op name" m))))])
        dispatch))

评估此时,会创建两个绑定 - 两个位置被搁置,一个我们以后可以称为x,另一个称为y - 并且每个都填充了相应的值:对于x,它是放在那里的1,对于y - 2.到目前为止一直很好。

然后,输入letrec表格。它创建绑定,三个特殊位置,名为set-x!set-y!dispatch。每个地方都填充了相应的值,即创建的相应lambda函数。

以下是至关重要的部分:因为内部内部(let ([x 1] [y 2]) ...)形式,这三个函数中的每一个都知道关于这两个地方,那两个绑定,适用于xy。在xyset-x!使用set-y!dispatch时,实际所指的是x的地方,或者分别为y

这三个函数中的每一个也都知道由(letrec ...)创建的另外两个函数以及它自身。这就是letrec的工作原理。使用let,由它创建的名称只能了解封闭环境。

在创建三个函数之后,其中一个函数dispatch将作为整个事物的值返回,即原始调用(cons 1 2)

我们写了(cons 1 2),并获得了一个值,一个知道其他两个过程的过程dispatch,以及两个值位置x和{{1} }。

返回的值,此y内的dispatch创建了环境,我们可以调用 >以消息为参数,显示letrec'car'cdr'set-car!。没有别的。

停止。回去一步。 "环境" 。由'set-cdr!内的letrec表单创建的letrec - 已创建的"环境" - 创建了"环境& #34; 。我们可以将其视为两个嵌套框。两个嵌套的矩形,由let创建的外部矩形,两个位置(两个隔间,或"单元格" )留在其中;而内部的一个由let创建,有三个隔间,其中有三个单元。每个框对应于其代码片段,代码形式,如letrec(let ...),用于创建"绑定" 或关联名称和地点。

实际上,每个这样的"框"被称为环境框架;和所有嵌套的框,每个框都有它的单元格,称为环境

每个已定义的函数都可以访问框 - - 该函数也可以访问所有外框,其创建框恰好被封闭。就像代码形式一个位于另一个内部。这正是"范围"表示 - 已知名称的代码区域,该区域引用包含值的位置。

盒子里面的盒子里面的盒子......里面有隔间。没错,真的。

(letrec ...)

当返回 ________________________________ | | | (let ([ .... ] | | [ .... ]) | | ______________________ | | | (letrec | | | | ([ .... ] | | | | [ .... ] | | | | [ .... ]) | | | | ...... | | | | ...... | | | | ) | | | *----------------------* | | ) | *--------------------------------* 值时,这是在该内部环境框架中以该名称存储的过程,它还有一个指向由dispatch创建的内部框架的隐藏指针。该框架还有一个隐藏指针,指向封闭框的环境框架,由(letrec ...)形式创建。

输入(let ...)框(代码区域,即范围)时,会创建其框架。输入let框(范围)后,将创建框架。外盒的框架对封闭盒子的框架一无所知,在它之前创建。最里面的盒子框架可以访问周围所有盒子的所有框架,从它周围的框架开始。所以这是一种由内向外方式:内框的框架包含指向外框的框架,而外框(代码区域或范围)包含内部框(代码区域)。

因此,当我们调用letrec时,它会被逐步解释为

(((cons 1 2) 'set-car!) 17)

由于(((cons 1 2) 'set-car!) 17) => (( {dispatch where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}} 'set-car!) 17) => ( {(dispatch 'set-car!) where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}} 17) => ( {set-x! where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}} 17) => {(set-x! 17) where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}} => {(set! x 17) where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}} => {(set! x 17) where {E1: x=1, y=2 }} => {E1: x=17, y=2 } 实际更改了存储在单元格中的值,因此从现在开始,此更改将在程序的其余部分中显示:

set!

希望这个伪代码足够清晰。接着,

(define v (cons 1 2))
=>
{dispatch where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}}
;
((v 'set-car!) 17)
=>
{dispatch where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=17, y=2 }}}
;
(v 'car)
=>
({dispatch where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=17, y=2 }}} 'car)
=>
{ (dispatch 'car) where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=17, y=2 }}}
=>
{ x where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=17, y=2 }}}
=>
{ x where {E1: x=17, y=2 }}
=>
17

在这里,我们选择了顶级评估策略,以便每个新的顶级命令的环境框架都包含在前一个版本中。

(define v (cons 1 2))
=>
{dispatch where {E2: set-x!=..., set-y!=..., dispatch=...
          where {E1: x=1, y=2 }}}
;
(define z (cons v v))
=>
{dispatch where {E5: set-x!=..., set-y!=..., dispatch=...
          where {E4: x=v, y=v
          where {E3: v={dispatch where {E2: set-x!=..., set-y!=..., dispatch=...
                                 where {E1: x=1, y=2 }}} }}}}

所以它正确地找到了适当的环境框架(((z 'cdr) 'set-car!) 17) => ...... (z 'cdr) ...... => ...... {(dispatch 'cdr) where {E5: set-x!=..., set-y!=..., dispatch=... ...... where {E4: x=v, y=v ...... where {E3: v={dispatch where {E2: set-x!=..., set-y!=..., dispatch=... ...... where {E1: x=1, y=2 }}} }}}} ...... => ...... { x where {E5: set-x!=..., set-y!=..., dispatch=... ...... where {E4: x=v, y=v ...... where {E3: v={dispatch where {E2: set-x!=..., set-y!=..., dispatch=... ...... where {E1: x=1, y=2 }}} }}}} ...... => ...... { v where {E3: v={dispatch where {E2: set-x!=..., set-y!=..., dispatch=... ...... where {E1: x=1, y=2 }}} }} ...... => ...... {dispatch where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}} ...... <= ... ((z 'cdr) 'set-car!) ... => ... {(dispatch 'set-car!) where {E2: set-x!=..., set-y!=..., dispatch=... ... where {E1: x=1, y=2 }}} ... => ... { set-x! where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}} ... <= (((z 'cdr) 'set-car!) 17) => { (set-x! 17) where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}} => { (set! x 17) where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}} => { (set! x 17) where {E1: x=1, y=2 }} => {E1: x=17, y=2 } 来变异(即改变存储在那里的值)。