9 — Store Passing
Tuesday, 04 February 2020
Presenters (1) F. Greenwald, Vire Patel (2) Vincent Carlino, John Nahil
Evaluating Declarations Once and For All
But, if the interpreter evaluates the initialization only once—
Good.
Lectures/9/odd.rkt
#lang racket (require "../8/ass-as-data.rkt") (require "old-interpreter.rkt") (define weird-example-1 (decl "f" (node + "f" 1) "f")) ; (interpret weird-example-1) (define weird-example-2 (decl "f" (fun "g" (fun "x" (call "g" (node * "x" "x")))) (decl "h" (call "f" "h") 10))) (interpret weird-example-2) (define weird-example-3 (call (decl "f" (fun "g" (fun "x" (call "g" (node * "x" "x")))) (decl "h" (call "f" "h") "h")) 10)) (interpret weird-example-3)
Figure 37 shows some more oddities of the implemented
language. Other dynamically typed languages—
See General [Organization] for "wat" presentations if you want to show off oddities in context.
Recursion From Assignment
(decl "f" 0 (sequ (set "f" (fun "x" (if-0 "x" 1 (node * "x" (call "f" (node + "x" -1)))))) [list (call "f" 10)]))
Stop! What happened?
The assignment to "f" creates a cycle in the memory. When the call to the function eventually triggers a reference to "f", the value (not its meaning!) is the function-value in the location.
This creation of cycle is how recursion actually works in real implementations, though for many definitions at the same time.
Better.
Yours truly created a lambda calculus that proves the fix
point equation for this function. Talcott proved they denote the
same function in the canonical model.
Detour If you think the Y combinator from 4 —
Lectures/9/Y-bang.rkt
#lang racket (define Y! (lambda (f-maker) ;; no recursion, no self-application ((lambda (fixpoint) (set! fixpoint (f-maker (lambda (x) (fixpoint x)))) fixpoint) 'NONE))) ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;; plain recursive function for determining the length of a list (define the-lyst '("a" "b" "c")) (define recursive-length (lambda (lyst) (if (empty? lyst) 0 (+ 1 (recursive-length (rest lyst)))))) (recursive-length the-lyst) ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;; using the Y! operator to create a fixpoint via mutation (define imperative-length ;; is _not_ recursive (Y! (lambda (g) (lambda (lyst) (if (empty? lyst) 0 (+ 1 (g (rest lyst)))))))) (imperative-length the-lyst)
Problems with the Interpretation of AssExpr
(decl "f" (node + "f" 1) "f")
Stop! Why?
The problem is that the truly initial value in the location of a declared variable may escape via a premature interpretation.
But, depending on where it goes, this may not immediately or perhaps never raise an exception.
Stop! Does it help to stick a proper AssExpr value into this location?
One solution is to trap every variable reference and check for #f; if the check succeeds, the interpreter can warn the programmer of this mistake.
Another solution is to identify syntactic values and use only those for initialization expressions. This restriction disallows the above program.
Stop! Does it truly restrict a programmer’s ability to express thoughts clearly?
Do Boxed Values Explain Mutation?
No.
Stores
PL researchers firmly embrace the idea that a mathematical representation
of mutable variables—
A mutable variable in a program has two distinct attributes: its scope and its current value. When the interpreter deals with a variable reference in isolation, the two attributes are determined by different aspects of the variable’s context. So the question is how to represent this contextual information.
contextual information is represented by an additional argument to the interpreter.
the environment is an ordinary accumulator that is passed along with every interpretive function/method call;
the store is a result accumulator.
; FVExpr Environment Store ->* Value Store
Stop! Use one of the previous examples or a simplified one to justify that Store must be a result.
Now the question is how the two accumulators relate. Intuitively, the store represents the memory of a computer, that is, a bunch of addressable memory locations in which actual values reside. Mathematically, this is an association between addresses and values. In PL, an address is usually called a location. In turn, this suggests that instead of boxed values, the revised interpreter sticks locations into the environment and uses the store to look up the current value of a location.
Where do locations come from? The interpreter must know which locations have already been used for some variable and which ones are free to use when the interpreter encounters a declaration or a function parameter. If we ignore the problem that computers have a finite number of locations, we can just count up from 0 to infinity as long as we know which locations are in use.
To summarize, the store is an association of locations and values and supplies three functions: one for allocating a fresh location, one for (re)associating a value with a location, and one for retrieving the value given some variable’s location.
Lectures/9/store.rkt
#lang racket (provide #; {type Store} #; {type Location} plain ;; Store #; {Store Value ->* Location Store} alloc #; {Store Location -> Value} retrieve #; {Store Location Value -> Store} update) ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (require "../4/possible-values.rkt") (module+ test (require rackunit)) ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (define plain '()) (define START -1) (define (alloc store val) (define last-address-used (apply max START (map first store))) (define new (+ last-address-used 1)) (values new (cons (list new val) store))) (define (retrieve store loc) (define r (assoc loc store)) (if r (second r) (error 'store-lookup "dangling pointer ~e" loc))) (define (update store loc val) (cons (list loc val) store))
Figure 39 displays a completely functional representation of the Store. You can understand it as “executable mathematics.”
Stores and Store-Passing
Given a store, we now have to figure out when to allocate locations for variables, when and how to look into locations, and when and how to update them.
Lectures/9/interpreter.rkt
#lang racket ;; an interpreter that uses store passing ;; the meaning of Variables are Locations ;; .. NEVER changes because their scope never changes ;; the meaning of Locations are Values ;; .. which change to represent mutation ;; INTERPRET RHS OF DECL ONLY ONCE (require "../6/environment.rkt") (require "../8/ass-as-data.rkt") (require "../8/examples.rkt") (require "../8/examples-fun.rkt") (require "../8/examples-ood.rkt") (require "../8/left-hand-side-value.rkt") (require "../4/possible-values.rkt") (require "store.rkt") (require SwDev/Debugging/spy) #; {Value = Number || (function-value parameter FExpr Env)} (define UNDECLARED "undeclared variable ~e") #; {FExpr -> Value} ;; determine the value of ae via a substitutione semantics (define (interpret ae0) #; {FExpr Env Store ->* Value Store} ;; ACCUMULATOR env tracks all declarations between ae and ae0 (define (interpret ae env store) (match ae [(? integer?) (values ae store)] [(node o a1 a2) (define-values (right0 store+) (interpret a2 env store)) (define right (number> right0)) (define-values (left0 store++) (interpret a1 env store+)) (define left (number> left0)) (values (o left right) store++)] [(decl x a1 a2) (define-values (loc store+) (alloc store #f)) (define env++ (add x loc env)) (define-values (val store++) (interpret a1 env++ store+)) (interpret a2 env++ (update store++ loc val))] [(? string?) (if (defined? ae env) (values (retrieve store (lookup ae env)) store) (error 'value-of UNDECLARED ae))] [(call ae1 ae2) (define-values (right store+) (interpret ae2 env store)) (define-values (left store++) (interpret ae1 env store+)) (fun-apply (function> left) right store++)] [(fun para body) (values (function-value para body env) store)] [(if-0 t thn els) (define-values (test-value store+) (interpret t env store)) (if (and (number? test-value) (= test-value 0)) (interpret thn env store+) (interpret els env store+))] [(set lhs rhs) (define loc (lookup lhs env)) (define old (retrieve store loc)) (define-values (val store+) (interpret rhs env store)) (values old (update store+ loc val))] [(sequ fst rst) (define-values (_ store+) (interpret fst env store)) (interpret rst env store+)])) #; {Value Value Store ->* Value Store} (define (fun-apply function-representation argument-value store) (match function-representation [(function-value fpara fbody env) (define-values (loc store+) (alloc store argument-value)) (interpret fbody (add fpara loc env) store+)])) (define-values (result store) (interpret ae0 empty plain)) result) #; {Any -> Number} (define (number> x) (if (number? x) x (error 'interpreter "number expected, given ~e " x))) #; {Any -> Function} (define (function> x) (if (function-value? x) x (error 'interpreter "closure expected, given ~e " x))) ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (provide interpret) ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (module+ test (require rackunit) (check-equal? (interpret (if-0 0 1 2)) 1) (check-equal? (interpret (if-0 1 0 2)) 2) (check-equal? (interpret example1) (example1-asl)) (check-equal? (interpret example1) (example1-object)))