mor*_*ode 3 evaluation scheme sicp lexical-scope
SICP 中的练习 3.20:
绘制环境图来说明表达式序列的求值
Run Code Online (Sandbox Code Playgroud)(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)
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 指针的过程对象,过程对象的汽车被更改。
我希望这是可以理解的。我的解决方案正确吗?
以下是您的具体问题的答案。
\n我将全局变量重命名x为v以避免混淆。
\n\nRun Code Online (Sandbox Code Playgroud)\n(define v (cons 1 2))\napply cons
\n
\ncons 创建名为 e1 的环境 - 全局环境是封闭环境
\nx 绑定到 1
\ny 绑定到 2
\nset-x!, set-y! 和dispatch各有一个指向e1的指针
\ndispatch绑定到v全局环境中的名称
正确的。
\n\n\nRun Code Online (Sandbox Code Playgroud)\n(define z (cons v v))\napply cons
\n
\ncons 创建 e2 - 全局封闭
\nx 相对于全局(共享)绑定到 v
\ny 相对于全局(共享)绑定到 v
\nset-x!, set-y! 和dispatch各有一个指向e2的指针
\ndispatch绑定到z全局环境中的名称
正确的。
\n\n\nRun Code Online (Sandbox Code Playgroud)\n(set-car! (cdr z) 17)\n申请定车!
\n
\n设置汽车!创建 e3 - 全局是封闭的
不。
\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我将首先重写您的cons函数,作为等效函数,只是更详细一点
(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)))\nRun Code Online (Sandbox Code Playgroud)\n更多地强调它的值方面,lambda 函数也是值,它们可以被创建、命名和返回。现在,以上意味着(cons 1 2)在我们的代码中编写与编写相同
(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))\nRun Code Online (Sandbox Code Playgroud)\n当它被求值时,会创建两个绑定 \xe2\x80\x93 并预留两个位置,一个我们稍后可以称为x,另一个称为y\xe2\x80\x93 ,并且每个绑定都填充有其对应的值:因为x它是放在那里的 1,而y\xe2\x80\x93 是 2。到目前为止一切顺利。
然后,letrec输入表格。它创建了它的绑定,它的三个特殊位置,名为set-x!、set-y!和dispatch。每个位置都填充有其对应的值,即创建的对应 lambda 函数。
这是关键部分:因为它是在外部形式内(let ([x 1] [y 2]) ...)完成的,所以这三个函数中的每一个都知道这两个位置,这两个绑定, forx和y。每当xory被set-x!、set-y!、 or使用时,实际分别指的是 for 、 或 for 的dispatch位置。xy
这三个函数中的每一个也都知道另外两个函数以及它自己,由(letrec ...). 就是这样letrec工作的。对于let,它创建的名称仅了解封闭环境。
创建三个函数后,其中之一 ,dispatch作为整个函数的值(即原始调用 的值)返回(cons 1 2)。
我们编写了(cons 1 2),并返回了一个值,该过程dispatch了解其他两个过程,也了解两个值位置x和y。
这个dispatch返回值,这个过程在letrec创建的环境中被称为,我们可以用消息作为参数来调用它,该消息读取'car,,,,或。没有别的。'cdr'set-car!'set-cdr!
停止。退一步。“环境”。创建letrec的“环境” ,由该创建的“环境”letrec内的形式创建。我们可以将其想象为两个嵌套的盒子。两个嵌套的矩形,外部的矩形由 生成,其中留有两个地方(两个隔间,或“单元格”);内部的一个由 创造,有三个隔间,里面有三个细胞。每个框对应于其代码片段,代码形式如或,创建“绑定”或名称和位置的关联。letletletrec(let ...)(letrec ...)
实际上,每个这样的“盒子”都被称为环境框架;所有嵌套的盒子,每个盒子都有它的单元格,一起被称为环境。
\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 *--------------------------------*\nRun Code Online (Sandbox Code Playgroud)\n当dispatch返回值时,该值是存储在该内部环境框架中的该名称下的过程,它还有一个指向由 . 创建的内部框架的隐藏指针(letrec ...)。该框架还有一个隐藏的指针,指向由表单创建的封闭框的环境框架(let ...)。
当let输入框(代码区域,即范围)时,它的框架就被创建。当letrec输入框(范围)时,它的框架就被创建了。外部盒子的框架对在其之前创建的封闭盒子的框架一无所知。最里面的盒子的框架可以访问它周围所有盒子的所有框架,从紧邻它周围的盒子开始。因此,这是一种由内而外的方式:内部框的框架包含指向外部框框架的指针,而外部框(代码区域或范围)包含内部框(代码区域)。
所以当我们调用时(((cons 1 2) 'set-car!) 17),它逐渐被解释为
(((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 \nRun Code Online (Sandbox Code Playgroud)\n因为set!实际上更改了存储在单元格中的值,所以从现在开始,此更改将在程序的其余部分中可见:
(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\nRun 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 }}} }}}}\nRun 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 }\nRun Code Online (Sandbox Code Playgroud)\n因此它正确地找到适当的环境框架,E1进行变异(即更改存储在那里的值)。