在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的指针,因此过程对象的汽车被更改。
我希望这是可以理解的。我的解决方案是否正确?
答案 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]) ...)
形式,这三个函数中的每一个都知道关于这两个地方,那两个绑定,适用于x
和y
。在x
,y
或set-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 }
来变异(即改变存储在那里的值)。