eqn1
eqn2
...
eqnn
------------------------------------
eqnconsequent
let f = proc (x) (x x) in (f f)Felix in mid-discussion revised this to look like
let f = let y = -(5,2) in proc(x) (x x) in (f f)
b_add
transformation(This is an interactive exercise exploring details of the EXPLICIT-REFS language and the implications of reasoning about programs that modify the Store.)
You can find Ben's transformation here: b-add-transform.
Our goal in exploring the IMPLICIT-REFS language is to understand how a language designer might choose to package common patterns by tailoring a language around those patterns.
In EXPLICIT-REFS, there is a lot of notational overhead in allocating locations in the store and referring back to them.
For example, a program that accumulates a value in a counter might have code like:
let tally = newref(0) in let incr = proc(x) begin setref(tally, -(deref(tally),-(0,x))); deref(tally) end in ...
Some programmers prefer a simpler way to allocate and refer to mutable storage.
To achieve this, we are going to make a new, different extension of the LETREC language. We will break away from our former pattern of making the Denoted Values equivalent to the Expressed Values.
In our new language, IMPLICIT-REFS, every variable will denote a reference to a location in the store.
Such references will not be first-class values; we will not, for example, be able to make a procedure that directly produces a reference as its result. We will also not be able to store references directly into the Store itself; we will only be able to store values such as integers, booleans, and procedures.
Question: what was our representation for the domain of references in the EXPLICIT-REFS interpreter? (No one knew, so we left it aside.)
setref
setref
is
a construct in the EXPLICIT-REFS language
for mutating the store, not binding a variable.)
let
letrec
proc
(which is tied to invocation, ie call expressions)newref
form, we will have to modify
the above three spots in our interpreter implementation to do
allocation.
How about deref
? Well, now every variable
is associated with a reference, and the only way to
get a reference is via a variable, so we can tie
location dereferencing to variable lookup.
That is, now evaluation of a variable expression
will result in both a lookup in the environment ρ
for the reference associated with the variable
and a lookup in the store σ
for the value associated with the location; a "two-level" lookup
for the value associated with the variable.
How about setref
?
We cannot just do away with this the same way we did
newref
and deref
;
mutating the store is a fundamental operation that
we want to keep (otherwise what's the point of
adding a store).
<expression> ::= set <identifier> = <expression>
Okay, lets see about implementing this language.
let x = 3 in begin set x = 4; x end
(That's pretty easy: 4
)
let x = 3 in let f = proc (y) x in begin set x = 4; (f 5) end
(A little harder. Some people said3
, some said4
. Why is it4
? IMPLICIT-REFS is a tricky language!)
let x = 3 in let f = proc (y) begin set y = 4; x end in (f x)
(Who knows? Maybe it should be4
. Or maybe it should be3
. It depends on whether the modification toy
also changesx
, which depends on whether we are using call-by-value or call-by-reference. (See the book.) We will use call-by-value, so this should result in3
.)
tests.scm
.
(define test-list '(... (simple-set-1 "let x = 3 in begin set x = 4; x end" 4) (simple-set-2 "let x = 3 in let f = proc (y) x in begin set x = 4; (f 5) end" 4) (simple-set-3 "let x = 3 in let f = proc (y) begin set y = 4; x end in (f x)" 3) ...))
top.scm
module and
hit the "Run" button.
incorrect answers on tests: (simple-set-1 simple-set-2 simple-set-3)
lang.scm
module.
(define the-grammar '((... (expression ("set" identifier "=" expression) set-exp)
interp.scm:25:6: cases: missing cases for the following variants: set-exp in: ...Again, unsurprising; we added new kinds of abstract syntax to the language, so the
cases
expressions that dispatch on which kind
of abstract syntax trees they encounter need to be updated.
interp.scm
.
We do not know how to implement this code, but we can put some
code in as a place holder that will immediately error if the code
is ever executed.
interp.scm
(define value-of (lambda (exp env) (cases expression exp ... (set-exp (id exp) (eopl:error 'set-exp "unimplemented"))
top.scm
module and
hit the "Run" button.
incorrect answers on tests: (simple-set-1 simple-set-2 simple-set-3)
set
form, but we were assuming that we already had a begin
form available in the language. Whoops!
begin
.
(This is not an absurd option, though it would mean that
we would have to be careful about what the order of evaluation
is in at least one of our other constructs, and probably
all of them. It is better to have a certain special form
that is dedicated to the purpose of indicating
that evaluation order is significant.)
begin
as a feature
as well.
lang.scm
(define the-grammar '((... (expression ("begin" expression (arbno ";" expression) "end") begin-exp)
top.scm
module and
hit the "Run" button.
interp.scm:25:6: cases: missing cases for the following variants: begin-exp in: ...
cases
expressions that dispatch on which kind
of abstract syntax trees they encounter need to be updated.
interp.scm
with another
bit of code that will error if it is ever executed.
(define value-of (lambda (exp env) (cases expression exp ... (begin-exp (exp1 exps) (eopl:error 'begin-exp "unimplemented"))
top.scm
module and
hit the "Run" button.
incorrect answers on tests: (simple-set-1 simple-set-2 simple-set-3)Further inspection reveals that the problem is:
actual outcome: begin-exp: unimplemented incorrectGreat! This is an error we anticipated.
begin
? NO!
begin
.
test.scm
(define test-list '(... (begin-exp-1 "let x = 3 in begin x end" 3) (begin-exp-2 "let x = 3 in let y = 4 in begin x; y end" 4)(Testing
begin
properly is a little more subtle than the
above, since any implementation that returns the value of its
final expression will be accepted by the above tests, but
this is more of a sanity-check.)
top.scm
; no surprise,
our begin
tests fail, because we haven't implemented
it yet.
begin
in interp.scm
(define value-of (lambda (exp env) (cases expression exp ... (begin-exp (exp1 exps) (letrec ((begin-loop ;; : Exp Listof[Exp] -> ExpVal (lambda (exp exps) (cond ((null? exps) (value-of exp env)) (else (begin (value-of exp env) (begin-loop (car exps) (cdr exps)))))))) (begin-loop exp1 exps)))
begin
passed.
store.scm
.
We can just snarf the one from the EXPLICIT-REFS interpreter,
since both languages will use the same basic abstraction
to represent a Store.
data-structures.scm
as follows:
(define-datatype environment environment? (empty-env) (extend-env (bvar symbol?) (bval reference?) ;; <-- (saved-env environment?)) (extend-env-recursively (id symbol?) (bvar symbol?) (body expression?) (saved-env environment?)))We discover that
reference?
is unbound in this module,
because we need to add a line to (require "store.scm")
at the top of the data-structures.scm
module.
top.scm
.
Now, lots of things break:
incorrect answers on tests: (positive-const negative-const simple-arith-1 nested-arith-left nested-arith-right test-var-1 test-var-2 test-var-3 if-true if-false if-eval-test-true if-eval-test-false if-eval-test-true-2 if-eval-test-false-2 simple-let-1 eval-let-body eval-let-rhs simple-nested-let check-shadowing-in-body check-shadowing-in-rhs apply-proc-in-rator-pos apply-simple-proc let-to-proc-1 nested-procs nested-procs2 y-combinator-1 simple-letrec-1 simple-letrec-2 simple-letrec-3 HO-nested-letrecs begin-exp-1 begin-exp-2 simple-set-1 simple-set-2 simple-set-3)Felix was not expecting this many failures.
let
, procedure application, and letrec
,
would need to change, and he went to each one and "fixed" them
in turn. But the tests never got better.
What Felix should have done, and eventually did do, is check what tests were failing, and determine why.
positive-const
, that is just the program "11"
.
Why would this start failing?
> (run "11") environments.scm::530: extend-env: bad value for bval field: #(struct:num-val 10)Clicking on the link that DrScheme provides (but also just by going to line 530 of the file
environments.scm
),
we see exactly where things are going wrong, in
environments.scm
(define init-env (lambda () (extend-env 'i (num-val 1) (extend-env 'v (num-val 5) (extend-env 'x (num-val 10) (empty-env))))))
(extend-env 'x (num-val 10) (empty-env))
in red.
(define init-env (lambda () (extend-env 'i (newref (num-val 1)) ;; <-- (extend-env 'v (newref (num-val 5)) ;; <-- (extend-env 'x (newref (num-val 10)) ;; <-- (empty-env))))))
(require "store.scm")
, and we are
ready to try to run our tests againtop.scm
.
Again, lots of things break, but this is more reasonable, because
now it is tests that involve variables that are breaking:
incorrect answers on tests: (test-var-1 test-var-2 test-var-3 simple-let-1 eval-let-body eval-let-rhs simple-nested-let check-shadowing-in-body check-shadowing-in-rhs apply-proc-in-rator-pos apply-simple-proc let-to-proc-1 nested-procs nested-procs2 y-combinator-1 simple-letrec-1 simple-letrec-2 simple-letrec-3 HO-nested-letrecs begin-exp-1 begin-exp-2 simple-set-1 simple-set-2 simple-set-3)
let
expressions,
since now we must allocate a location every time we bind
a variable.
interp.scm
:
(define value-of (lambda (exp env) (cases expression exp ... (let-exp (id rhs body) (let ((val (value-of rhs env))) (let ((loc (newref val))) (value-of body (extend-env id loc env)))))
newref
because we are missing a
line to (require "store.scm")
in the interp
module.
interp.scm
as follows:
apply-procedure
's contract needs to be corrected, and
value-of
's code needs to be changed:
;; apply-procedure : procedure * denval -> expval (define value-of (lambda (exp env) (cases expression exp ... (app-exp (rator rand) (let ((proc (expval->proc (value-of rator env))) (arg (value-of rand env))) (let ((loc (newref arg))) ;; <-- (apply-procedure proc loc)))) ;; <--
letrec
?
letrec
by creating
the closures on demand, during variable lookup.
environments.scm
:
(define apply-env (lambda (env search-sym) (cases environment env ... (extend-env-recursively (id bvar body saved-env) (if (eqv? search-sym id) (newref (proc-val (procedure bvar body env))) ;; <-- (apply-env saved-env search-sym))))))
top.scm
... but again everything
fails. Why?
let
, proc
,
or letrec
.
interp.scm
(define value-of (lambda (exp env) (cases expression exp ... (var-exp (id) (deref (apply-env env id)))
top.scm
...
incorrect answers on tests: (simple-set-1 simple-set-2 simple-set-3)
actual outcome: set-exp: unimplemented
set-exp
expressions, but forgot to actually put
the code in for them!
interp.scm
(define value-of (lambda (exp env) (cases expression exp ... (set-exp (id exp) (let ((val (value-of exp env)) (loc (apply-env env id))) (begin (setref! loc val) (num-val 23))))
top.scm
module.
no bugs found
let
-bound variables works, proc
-bound variables
does not cause things to blow up,
but may not actually work.Any last questions?
Last updated 22 February 2008.
Felix S Klock II