date: 2021-05-06
tags: PL
继续看 EOPL。
求值和产生 effect 的区别是什么呢?effect 是 global 的,整个计算都能观察到,并影响整个计算。
我们主要关心一个 effect:向某个内存位置赋值。
赋值和绑定的区别是什么呢?binding 是 local 的,但是赋值可能是全局的。
我们将把 memory 建模为从 location 到 storable values 的 finite map。一般来说 storable values 和 expressed values 是相同的。
表示 location 的数据结构称为 reference。reference 有时被称为 L-values,表示其往往出现在赋值语句的左边。类似地,expressed values 被称为 R-values。
在本章中,我们会考虑 2 种设计,explicit references 和 implicit references。
在 explicit references 中,我们会把 reference 作为一种新的 expressed value:
ExpVal = Int + Bool + Proc + Ref(ExpVal)
DenVal = ExpVal
我们要加入 3 个新操作:
newref
:分配新 location,并返回其 referencederef
:返回 reference 指向的 location 里面存储的值setref
:改变 reference 指向的 location 里面存储的值一个 EXPLICIT_REFS 语言的例子如下:
let g = let counter = newref(0)
in proc (dummy)
begin
setref(counter, -(deref(counter), -1));
deref(counter)
end
in let a = (g 11)
in let b = (g 11)
in -(a,b)
store-passing specification 中 store 会作为 value-of
的显示输入,并作为输出的一部分:
(value-of exp1 ρ σ0) = (val1,σ1)
根据这点,我们可以更新之前的 specification。
constant:
(value-of (const-exp n) ρ σ) = (n,σ)
difference:
注意,假设中两次 value-of
的 store 都不一样。
if:
这一节我们来看一下 newref
、deref
和 setref
的 specification。
首先是 newref
:
其次是 deref
:
最后是 setref
:
这里返回的 23 是个任意数。
我们不打算如 specification 指示的去把 store 作为 value-of
的输入输出,而是打算使用 Scheme(Racket)本身的 store。为了简单起见,我们把 store 做成了一个 list,reference 就是 list 的位置。
; empty-store : () → Sto
(define empty-store
(lambda () ’()))
; usage: A Scheme variable containing the current state
; of the store. Initially set to a dummy value.
(define the-store ’uninitialized)
; get-store : () → Sto
(define get-store
(lambda () the-store))
; initialize-store! : () → Unspecified
; usage: (initialize-store!) sets the-store to the empty store
(define initialize-store!
(lambda ()
(set! the-store (empty-store))))
; reference? : SchemeVal → Bool
(define reference?
(lambda (v)
(integer? v)))
从而得到了如下的实现:
; newref : ExpVal → Ref
(define newref
(lambda (val)
(let ((next-ref (length the-store)))
(set! the-store (append the-store (list val)))
next-ref)))
; deref : Ref → ExpVal
(define deref
(lambda (ref)
(list-ref the-store ref)))
; setref! : Ref × ExpVal → Unspecified
; usage: sets the-store to a state like the original, but with
; position ref containing val.
(define setref!
(lambda (ref val)
(set! the-store
(letrec
((setref-inner
; usage: returns a list like store1, except that
; position ref1 contains val.
(lambda (store1 ref1)
(cond
((null? store1)
(report-invalid-reference ref the-store))
((zero? ref1)
(cons val (cdr store1)))
(else
(cons
(car store1)
(setref-inner (cdr store1) (- ref1 1))))))))
(setref-inner the-store ref)))))
直接看代码就好,还是挺清楚的。
很多语言把 reference 和 dereference 的过程隐藏起来了,也就是说所有的变量表示的都是 reference:
ExpVal = Int + Bool + Proc
DenVal = Ref(ExpVal)
在每次 binding 的时候会创建 location,也就是每次的 procedure call, let
或 letrec
。
当 variable 出现在 expression 中时,我们会查看 environment 来查看它 bound 的 location,之后查看 store 来找到值。也就是我们的 var-exp
是一个双层的系统。
location 中的值可以通过 set
表达式来改变,也就是新语法:
Expression ::= set Identifier = Expression
assign-exp (var exp1)
这里的 Identifier 不是 expression 的一部分,所以不会被 dereference。
这种设计,我们称变量是可变的(mutable)。这种设计则被称为 call-by-value 或 implicit reference。大多数编程语言,包括 Scheme 在内,都采用了这种设计的某种变形。
let g = let count = 0
in proc (dummy)
begin
set count = -(count,-1);
count
end
in let a = (g 11)
in let b = (g 11)
in -(a,b)
上面是 implicit reference 的一个例子,可以和上面的 explicit 的例子比较一下。
首先是取值:
(value-of (var-exp var) ρ σ) = (σ(ρ(var)), σ)
然后是 assign,也就是 set
:
这里的 27 也是个随机数。
然后是 apply-procedure
:
(apply-procedure (procedure var body ρ) val σ)
= (value-of body [var = l]ρ [l = val]σ)
注意这里是没有垃圾回收的,每次调用都会使用新的 location。
照着 specification 写就行。里面还是要用到 4.2 中定义的 newref
,deref
和 setref!
的。
在练习 3.9 中,我们在语言中加入了 list,不过当时的 list 是不可变的(也就是没有 scheme 中的 set-car!
和 set-cdr!
)。在这一节中,我们将给 IMPLICIT-REFS 中加上 mutable pairs。这样 expressed value 变成:
ExpVal = Int + Bool + Proc + MutPair
DenVal = Ref(ExpVal)
MutPair = Ref(ExpVal) × Ref(ExpVal)
还有加入下列 interface:
newpair : Expval × Expval → MutPair
left : MutPair → Expval
right : MutPair → Expval
setleft : MutPair × Expval → Unspecified
setright : MutPair × Expval → Unspecified
我们知道 MutPair
的两个值的存储位置相邻,所以实际上我们可以只用左边的元素的 reference 表示整体的 reference。
如果 body 不使用其参数,那么我们就不需要 evaluate 它。
letrec infinite-loop (x) = infinite-loop(-(x,-1))
in let f = proc (z) 11
in (f (infinite-loop 0))
在上面这个例子中 infinite-loop
是不会停止的,但是在 lazy evaluation 的情况下,这个程序会直接返回 11,因为并不会 evalutate 参数的值。
下面让我们来把我们的语言改成 lazy evaluation 的。为了表明