了解评估的环境模型

mor*_*ode 3 evaluation scheme sicp lexical-scope

SICP 中的练习 3.20:

绘制环境图来说明表达式序列的求值

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


(set-car! (cdr z) 17)

(car x) 17
Run Code Online (Sandbox Code Playgroud)

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

我的眼睛被毁了所以我不能画画。相反,我会尽力想象环境模型如何演变。

首先,这是程序对的实现。

(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)
Run Code Online (Sandbox Code Playgroud)

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

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

(define x (cons 1 2))
Run Code Online (Sandbox Code Playgroud)

apply cons
cons 创建的名为 e1 的环境 - 全局环境是封闭环境
x 绑定到 1
y 绑定到 2
set-x!, set-y! 和dispatch各有一个指向e1的指针,
dispatch绑定到全局环境中的名字x

(define z (cons x x))
Run Code Online (Sandbox Code Playgroud)

apply cons
cons create e2 - 全局包含
相对于全局(共享)绑定到 x 的 x 相
对于全局(共享)绑定到 x 的 y
set-x!, set-y! 和dispatch各有一个指向e2的指针,
dispatch绑定到全局环境中的名字z

(set-car! (cdr z) 17)
Run Code Online (Sandbox Code Playgroud)

申请定车!
定车!创建 e3 - 全局封闭
z 相对于全局
apply cdr
cdr 绑定到 (cdr z) 创建 e4 - 全局封闭
z 相对于全局
调度绑定到 z 创建 e5 - e2 封闭
m 绑定到 'cdr
return x 相对于 e2。
x 相对于全局共享 x(以及相对于 e1 的 x)
返回到 e3
新值绑定到 17
调度创建 e6 - e2 包含
m 绑定到 'set-car!
设置-x!相对于e2返回
应用set-x!
设置-x!创建 e7 - e2 包含
新值,绑定到 17 将
x 相对于 e2 设置为 17,
因为 x 是 e1,e2 中的 x 和全局中的 x 共享一个具有指向 e1 指针的过程对象,过程对象的汽车被更改。

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

Wil*_*ess 5

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

\n

我将全局变量重命名xv以避免混淆。

\n
\n
 (define v (cons 1 2))\n
Run Code Online (Sandbox Code Playgroud)\n

apply cons
\ncons 创建名为 e1 的环境 - 全局环境是封闭环境
\nx 绑定到 1
\ny 绑定到 2
\nset-x!, set-y! 和dispatch各有一个指向e1的指针
\ndispatch绑定到v全局环境中的名称

\n
\n

正确的。

\n
\n
 (define z (cons v v))\n
Run Code Online (Sandbox Code Playgroud)\n

apply cons
\ncons 创建 e2 - 全局封闭
\nx 相对于全局(共享)绑定到 v
\ny 相对于全局(共享)绑定到 v
\nset-x!, set-y! 和dispatch各有一个指向e2的指针
\ndispatch绑定到z全局环境中的名称

\n
\n

正确的。

\n
\n
 (set-car! (cdr z) 17)\n
Run Code Online (Sandbox Code Playgroud)\n

申请定车!
\n设置汽车!创建 e3 - 全局是封闭的

\n
\n

不。

\n

(在下文中,不使用任何代码格式 Markdown,以使视障人士的噪音降至最低)。

\n

首先,评估(cdr z)。它相当于(z'cdr)。z 必然从 e2 帧调度。这会分派到 e2 的 y,并收到消息“cdr”。这会访问e2 环境帧中的 y 槽,该槽保存全局环境中的 v 值。

\n

接下来,对 (set-car! v 17) 进行评估。它相当于 ((v 'set-car!) 17)。v 绑定到e1 帧的调度。收到“定车!” 消息,它发送到 e1 的 set-x!功能。因此,这将使用 e1 的 set-x! 调用 (set-x!17)。这又在环境框架 e1 内调用 (set!x 17)。因此它访问并修改环境框架 e1 中的“x”槽!

\n

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

\n

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

\n

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

\n
\n

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

\n

我将首先重写您的cons函数,作为等效函数,只是更详细一点

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

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

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

被求值时,会创建两个绑定 \xe2\x80\x93 并预留两个位置,一个我们稍后可以称为x,另一个称为y\xe2\x80\x93 ,并且每个绑定都填充有其对应的值:因为x它是放在那里的 1,而y\xe2\x80\x93 是 2。到目前为止一切顺利。

\n

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

\n

这是关键部分:因为它是在外部形式(let ([x 1] [y 2]) ...)完成的,所以这三个函数中的每一个都知道这两个位置,这两个绑定, forxy。每当xoryset-x!set-y!、 or使用时,实际分别指的是 for 、 或 for 的dispatch位置。xy

\n

这三个函数中的每一个也都知道另外两个函数以及它自己,由(letrec ...). 就是这样letrec工作的。对于let,它创建的名称仅了解封闭环境。

\n

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

\n

我们编写了(cons 1 2),并返回了一个值,该过程dispatch了解其他两个过程,也了解两个值位置xy

\n

这个dispatch返回值,这个过程在letrec创建的环境中被称为,我们可以用消息作为参数来调用它,该消息读取'car,,,,或。没有别的。'cdr'set-car!'set-cdr!

\n

停止。退一步。“环境。创建letrec“环境” ,由该创建的“环境”letrec内的形式创建。我们可以将其想象为两个嵌套的盒子。两个嵌套的矩形,外部的矩形由 生成,其中留有两个地方(两个隔间,或“单元格”);内部的一个由 创造,有三个隔间,里面有三个细胞。每个框对应于其代码片段,代码形式如或,创建“绑定”或名称和位置的关联。letletletrec(let ...)(letrec ...)

\n

实际上,每个这样的“盒子”都被称为环境框架;所有嵌套的盒子,每个盒子都有它的单元格,一起被称为环境

\n

每个定义的函数都可以访问创建框 \xe2\x80\x93 ,创建它的框\xe2\x80\x93 ,并且该函数还可以访问其创建框恰好包含在其中的所有外部框。就像代码表单位于另一个表单内一样。这正是“范围”的含义 \xe2\x80\x93 代码区域,其中名称已知,指的是保存值的位置。

\n

盒子套在盒子里,盒子套在盒子里……里面有隔间。真的,仅此而已。

\n
       ________________________________\n      |                                |\n      | (let   ([ .... ]               |\n      |         [ .... ])              |\n      |    ______________________      |\n      |   | (letrec              |     |\n      |   |      ([ .... ]       |     |\n      |   |       [ .... ]       |     |\n      |   |       [ .... ])      |     |\n      |   |   ......             |     |\n      |   |   ......             |     |\n      |   |   )                  |     |\n      |   *----------------------*     |\n      |   )                            |\n      *--------------------------------*\n
Run Code Online (Sandbox Code Playgroud)\n

dispatch返回值时,该值是存储在该内部环境框架中的该名称下的过程,它还有一个指向由 . 创建的内部框架的隐藏指针(letrec ...)。该框架还有一个隐藏的指针,指向由表单创建的封闭的环境框架(let ...)

\n

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

\n

所以当我们调用时(((cons 1 2) 'set-car!) 17),它逐渐被解释为

\n
(((cons 1 2) 'set-car!) 17)\n=>\n(( {dispatch where {E2: set-x!=..., set-y!=..., dispatch=... \n                    where {E1: x=1, y=2 }}} 'set-car!) 17)\n=>\n(  {(dispatch 'set-car!)\n             where {E2: set-x!=..., set-y!=..., dispatch=... \n                    where {E1: x=1, y=2 }}} 17)\n=>\n(  {set-x!   where {E2: set-x!=..., set-y!=..., dispatch=... \n                    where {E1: x=1, y=2 }}} 17)\n=>\n{(set-x! 17) where {E2: set-x!=..., set-y!=..., dispatch=... \n                    where {E1: x=1, y=2 }}}\n=>\n{(set! x 17) where {E2: set-x!=..., set-y!=..., dispatch=... \n                    where {E1: x=1, y=2 }}}\n=>\n{(set! x 17) where {E1: x=1, y=2 }}\n=>\n{E1: x=17, y=2 }\n     \n
Run Code Online (Sandbox Code Playgroud)\n

因为set!实际上更改了存储在单元格中的值,所以从现在开始,此更改将在程序的其余部分中可见:

\n
(define v (cons 1 2))\n=>\n{dispatch where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}}\n;\n((v 'set-car!) 17)\n=>\n{dispatch where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=17, y=2 }}}\n;\n(v 'car)\n=>\n({dispatch where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=17, y=2 }}} 'car)\n=>\n{ (dispatch 'car) where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=17, y=2 }}}\n=>\n{ x where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=17, y=2 }}}\n=>\n{ x where {E1: x=17, y=2 }}\n=>\n17\n
Run Code Online (Sandbox Code Playgroud)\n

希望这个伪代码足够清晰。下一个,

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

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

\n
(((z 'cdr) 'set-car!) 17)\n=>\n...... (z 'cdr)\n...... =>\n...... {(dispatch 'cdr) where {E5: set-x!=..., set-y!=..., dispatch=...\n......     where {E4: x=v, y=v\n......     where {E3: v={dispatch where {E2: set-x!=..., set-y!=..., dispatch=...\n......                            where {E1: x=1, y=2 }}} }}}}\n...... =>\n...... { x where {E5: set-x!=..., set-y!=..., dispatch=...\n......     where {E4: x=v, y=v\n......     where {E3: v={dispatch where {E2: set-x!=..., set-y!=..., dispatch=...\n......                            where {E1: x=1, y=2 }}} }}}}\n...... =>\n...... { v where {E3: v={dispatch where {E2: set-x!=..., set-y!=..., dispatch=...\n......                            where {E1: x=1, y=2 }}} }}\n...... =>\n...... {dispatch where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}}\n...... <=\n... ((z 'cdr) 'set-car!)\n... =>\n... {(dispatch 'set-car!) where {E2: set-x!=..., set-y!=..., dispatch=...\n...                               where {E1: x=1, y=2 }}}\n... =>\n... { set-x! where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}}\n... <=\n(((z 'cdr) 'set-car!) 17)\n=>\n{ (set-x! 17) where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}}\n=>\n{ (set! x 17) where {E2: set-x!=..., set-y!=..., dispatch=... where {E1: x=1, y=2 }}}\n=>\n{ (set! x 17) where {E1: x=1, y=2 }}\n=>\n{E1: x=17, y=2 }\n
Run Code Online (Sandbox Code Playgroud)\n

因此它正确地找到适当的环境框架,E1进行变异(即更改存储在那里的值)。

\n