480 likes | 613 Vues
Assignments and Procs w/ Params. EOPL3 Chapter 4. Expressible vs. Denotable values. Expressible Values the language can express and compute these represented in the language's syntax as expressions Denotable Values represented in syntax by declarations
E N D
Assignments and Procs w/Params EOPL3 Chapter 4
Expressible vs. Denotable values • Expressible Values • the language can express and compute these • represented in the language's syntax as expressions • Denotable Values • represented in syntax by declarations • the names and their values saved within a data structure (called a namespace or environment or symbol table or activation record) • In some languages, these two are not equal. • Booleans are expressible in (early) FORTRAN, but not denotable. • Functions are denotable in many languages, but are not expressible. • In (functional subset of) Scheme, both value spaces are identical. • In (full) Scheme, variable references (pointers) are denotable but not expressible.
Assignment, LHS := RHS • l-value: left-, location, address, reference, … • r-value: right-, int, real, address, … • env and store allow one to describe the semantics of assignment in a purely functional style. (DENOTATIONAL SEMANTICS) • Object language structures are mapped to similar Scheme structures. (META-CIRCULAR INTERPRETER)
Sharing • sharing/aliasing • Point p = new Point(); • Point q = p; • call by reference • void f(Point p){…}; • f(q);
Side-effects in Scheme • Update variable: (set! var exp) • denotes location denotes value • In Scheme locations are denotable, but not expressible • Sequencing: • (begin exp1 exp2 … expn) • Ordering of expressions important
Local Binding: let • (letproc-id ([idinit-expr] ...) body ...+) • Defines a local procedure. Evaluates the init-exprs; these become arguments to the proc. The ids must be distinct. • (let fac ([n 10]) (if (zero? n) 1 (* n (fac (sub1 n))))) 3628800
Local Binding: let* • (let* ([idval-expr] ...) body ...+) • Similar to let, but evaluates the val-exprs one by one, creating a location for each id as soon as the value is available. The ids are bound in the remainingval-exprs as well as the bodys, and the ids need not be distinct; later bindings shadow earlier bindings. • (let* ([x 1] • [y (+ x 1)]) • (list y x)) (2 1)
Local Binding: letrec • (letrec ([idval-expr] ...) body ...+) • Similar to let, but the locations for all ids are created first and filled with #<undefined>, and all ids are bound in all val-exprs as well as the bodys. The ids must be distinct. • (letrec ((a b) (b 34) (c (+ b 5))) • (list a b c)) (#<undefined> 34 39) • (letrec ([is-even? (lambda (n) • (or (zero? n) • (is-odd? (sub1 n))))] • [is-odd? (lambda (n) • (and (not (zero? n)) • (is-even? (sub1 n))))]) • (is-odd? 11)) #t
Comparison: let, let*, letrec • (let/let*/letrec ((v1 e1 ) (v2 e2 ) … (vn en )) body ) • let • no vi is created until all ei are evaluated. • none of ei can refer to any vi • let* • e1 is evaluated; v1 created, bound to e1; • e2 is evaluated; v2 created, bound to e2; …; • ej can refer to earlier vi, i < j. • letrec • vi are created with #undefined as their value. • with the above in effect, e1, …, en are evaluated l-to-r • each vi is now bound to ei
Simulating Scheme letrec (letrec ((v1 exp1) (v2 exp2)) exp) ;; exp1 and exp2 are lambda-forms (let ((v1 ’*) (v2 ’*)) (set! v1 exp1) (set! v2 exp2) exp )
Env and Store • For functional subset, it is sufficient to model env as a function from ids to values. • For imperative programming that has both assignment and sharing, separating env and store is necessary. • env: identifier location • store: location value • assignment: location X value X store store
EXPLICIT-REFS • ExpVal = Int + Bool + Proc + Ref(ExpVal) • DenVal = ExpVal • Ref(ExpVal) == references to locations that contain expressed values. • newref: allocates a new location, returns a ref to it. • deref: dereferences • setref: changes the contents • This gives a clear account of • allocation, dereferencing, and mutation
Even and Odd Redone let x = newref(0) in letrec even(dummy) = if zero?(deref(x)) then 1 else begin setref(x, -(deref(x),1)); (odd 888) end odd(dummy)= if zero?(deref(x)) then 0 else begin setref(x, -(deref(x),1)); (even 888) end in begin setref(x,13); (odd 888) end
Hidden State 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 Specifications • [c = v]σ location c is mapped to v in store σ • (value-of exp1 ρ σ0) = (val1, σ1) • specification for diff-exp (value-of exp1 ρ σ0) = (val1, σ1) and (value-of exp2 ρ σ1) = (val2, σ2) implies(value-of (diff-exp exp1 exp2) ρ σ0)= ( [val1] – [val2], σ2) (caution: [] incorrect symbols)
conditional Let (value-of e1 ρ σ0) = (v1, σ1). Then (value-of (if-exp e1 e2 e3) ρ σ0) = (value-of e2 ρ σ1) if (expval->bool v1) = #t = (value-of e3 ρ σ1) if (expval->bool v1) = #f
newref, deref, and setref Expression ::= newref (Expression) AST: newref-exp (exp1) Expression ::= deref (Expression) AST: deref-exp (exp1) Expression ::= setref (Expression, Expression) AST: setref-exp (exp1 exp2)
Specs of newref • Given: • (value-of exp ρ σ0) = (val, σ1),lc !∈ dom(σ1 ) • (value-of (newref-exp exp) ρ σ0)= ((ref-vallc), [lc=val] σ1) • newref-exp evaluates its operand. Allocates a new location lc and stores val in that location. Then it returns a reference to a location lc that is new. This means that the new loc is not already in the domain of σ1.
Specs of deref • Given: (value-of exp ρ σ0) = (lc, σ1) • (value-of (deref-exp exp) ρ σ0) = (σ1(lc), σ1) • exp evaluation leaves the store in state σ1. The value of that argument should be a reference to a location lc. The deref-exp then returns the contents of lc in σ1 , without any further change to the store.
spec of setref • Given: • (value-of exp1 ρ σ0) = (lc, σ1) • (value-of exp2 ρ σ1) = (val, σ2) ;; note σ1 σ2 order • Then: • (value-of (setref-exp exp1 exp2) ρ σ0) = ( [23], [lc = val] σ2) ;; caution [] • setref-exp evaluates exp1 first, exp2 second. First value must be a reference to a location lc. • setref-exp then updates σ2 by putting val in location lc. It • could return anything; e.g. 23. • This expression is executed for its effect, not its value.
Implementation • state σ of the store as a Scheme value • represent the store as a list of expressed values, • keep the state in a single global variable • all the procedures of the impl have access. • This representation is extremely inefficient.
A naive model of the store 1/3 (define empty-store (lambda () ’())) (define the-store ’uninitialized) ; initially (define get-store (lambda () the-store)) (define initialize-store! (lambda () (set! the-store (empty-store))))
A naive model of the store 2/3 (define reference? (lambda (v) (integer? v))) (define newref (lambda (val) (let ((next-ref (length the-store))) (set! the-store (append the-store (list val))) next-ref))) (define deref (lambda (ref) (list-ref the-store ref)))
A naive model of the store 3/3 (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)))))
value-of-program (define value-of-program (lambda (pgm) (initialize-store!) (cases program pgm (a-program (exp1) (value-of exp1 (init-env))))))
value-of clauses explicit-ref ops (newref-exp (exp1) (let ((v1 (value-of exp1 env))) (ref-val (newref v1)))) (deref-exp (exp1) (let ((v1 (value-of exp1 env))) (let ((ref1 (expval->ref v1))) (deref ref1)))) (setref-exp (exp1 exp2) (let ((ref (expval->ref (value-of exp1 env)))) (let ((val2 (value-of exp2 env))) (begin (setref! ref val2) (num-val 23)))))
IMPLICIT-REFS • ExpVal = Int + Bool + Proc • references are no longer expressed values. • DenVal = Ref(ExpVal) • Locations are created with each binding operation: • at each procedure call, let, or letrec. • This design is called call-by-value, or implicit references. • Expression ::= set Identifier = Expression • AST: assign-exp (var exp1) • Assignment statement • Variables are mutable.
IMPLICIT-REFS examples let x = 0 in letrec even(dummy) = if zero?(x) then 1 else begin set x = --(x,1); (odd 888) end odd(dummy) = if zero?(x) then 0 else begin set x = --(x,1); (even 888) end 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)
value-of specs • (value-of (var-exp var) ρ σ) = ( σ(ρ(var)), σ) • environment ρ binds variables to locations • Given: (value-of exp1 ρ σ0) = (val1, σ1) • Then, (value-of (assign-exp var exp1) ρ σ0) = ( [27], [ρ(var) = val1] σ1) ;; caution: [] • For procedure call, the rule becomes(apply-procedure (procedure var body ρ) val σ)= (value-of body [var = lc]ρ [lc = val]σ )
MUTABLE-PAIRS • A Language with Mutable Pairs • Reading Assignment
Parameter-Passing Variations • When a procedure body is executed, • its formal parameter is bound to a denoted value. • It must be passed from the actual argument in the call. • Natural parameter passing • the denoted value is the same as the expressed value of the actual parameter (EOPL3 page 75). • Call-by-value • the denoted value is a reference to a location containing the expressed value of the actual parameter (EOPL3 section 4.3).
call-by-value v. -by-ref • Under call-by-value, a new reference is created for every evaluation of an operand • Under call-by-reference, a new reference is created for every evaluation of an operand other than a variable.
CALL-BY-REFERENCE let p = proc (x) set x = 4 in let a = 3 in begin (p a); a end let f = proc (x) set x = 44 in let g = proc (y) (f y) in let z = 55 in begin (g z); z end • next prog: 11 versus --11 let swap = proc (x) proc (y) let temp = x in begin set x = y; set y = temp end in let a = 33 in let b = 44 in begin ((swap a) b); --(a,b) end
call-by-reference • ExpVal = Int + Bool + Proc • DenVal =Ref(ExpVal) • a new location is created for every evaluation of an operand other than a variable.
call-by-ref implementation (define apply-procedure (lambda (proc1 val) (cases proc proc1 (procedure (var body saved-env) (value-of body (extend-envvarval saved-env)))))) (call-exp (rator rand) (let ((proc (expval->proc (value-of ratorenv))) (arg (value-of-operand rand env))) (apply-procedure proc arg)))
value-of-operand (define value-of-operand (lambda (exp env) (cases expression exp (var-exp (var) (apply-envenvvar)) (else (newref (value-of exp env))))))
variable aliasing let b = 3 in let p= proc (x) proc(y) begin set x = 4; y end in ((p b) b) • both x and y refer to the same location • Yields 4 • aliasing makes it difficult to understand programs.
Lazy Evaluation • Under lazy evaluation, an operand in a procedure call is not evaluated until it is needed by the procedure body. • Sometimes in a given call a procedure never evaluates some of its formal parameters. • This can potentially avoid non-termination. letrec infinite-loop (x) = infinite-loop(--(x, --1)) in let f = proc (z) 11 in (f (infinite-loop 0)) • infinite-loop does not terminate. • above prog returns 11 under lazy eval
Lazy Evaluation Terms • A thunk is a procedure with no arguments. • One can delay (perhaps indefinitely) the evaluation of an operand by encapsulating it as a thunk. • Freezing: forming thunks • Thawing: evaluating thunks
call-by-name, -by-need • call-by-name: invoke the thunk every time the parameter is referred to. • In the absence of side effects this is a waste of time, since the same value is returned each time. • call-by-need: record the value of each thunk the first time it is invoked, andthereafter refers to the saved value. • an example of memoization.
CALL-BY-NAME • An operand is frozen when it is passed unevaluated to the procedure • Operand is thawed when procedure evaluates it • DenVal = Ref(ExpVal + Thunk) • ExpVal = Int + Bool + Proc (define-datatype thunkthunk? (a-thunk (exp1 expression?) (env environment?)))
value-of-operand (define value-of-operand (lambda (exp env) (cases expression exp (var-exp (var) (apply-envenvvar)) (else (newref (a-thunk exp env))))))
call by name design (var-exp (var) (let ((ref1 (apply-envenvvar))) (let ((w (deref ref1))) (if (expval? w) w (value-of-thunk w)))))
value-of-thunk: Thunk→ExpVal (define value-of-thunk (lambda (th) (cases thunkth (a-thunk (exp1 saved-env) (value-of exp1 saved-env))))
call by need • Alternatively, once we find the value of the thunk, we can install that expressed value in the same location, so that the thunk will not be evaluated again. • This is an instance of a general strategy called memoization.
memoization (var-exp (var) (let ((ref1 (apply-envenvvar))) (let ((w (deref ref1))) (if (expval? w) w (let ((val1 (value-of-thunk w))) (begin (setref! ref1 val1) val1))))))
Lazy Evaluation Summary • In the absence of (side) effects, it supports reasoning about programs in a particularly simple way. • The effect of a procedure call can be modeled by replacing the call with the body of the procedure, with every reference to a formal parameter in the body replaced by the corresponding operand. • This evaluation strategy is the basis for the lambda calculus, where it is called β-reduction. • Unfortunately, call-by-name and call-by-need make it difficult to determine the order of evaluation, which in turn is essential to understanding a program with effects. • Thus lazy evaluation is popular in functional programming languages (those with no effects), and rarely found elsewhere.