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) CS784(PM) • 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. 2 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. (METACIRCULAR INTERPRETER) CS784(PM) 3 Sharing • sharing/aliasing • Point p = new Point(); • Point q = p; CS784(PM) • call by reference • void f(Point p){…}; • f(q); 4 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 CS784(PM) 5 Local Binding: let • (let proc-id ([id init-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 CS784(PM) 6 Local Binding: let* • (let* ([id val-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 remaining val-exprs as well as the bodys, and the ids need not be distinct; later bindings shadow earlier bindings. 1. (let* ([x 1] 2. [y (+ x 1)]) 3. (list y x)) (2 1) CS784(PM) 7 Local Binding: letrec • • (letrec ([id val-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. 1. (letrec ((a b) (b 34) (c (+ b 5))) 2. (list a b c)) (#<undefined> 34 39) 3. 4. 5. 6. 7. 8. 9. (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 CS784(PM) 8 Comparison: let, let*, letrec • (let/let*/letrec ((v1 e1 ) (v2 e2 ) • let … (vn en )) body ) • 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 CS784(PM) 9 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 ) CS784(PM) 10 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 CS784(PM) 11 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 CS784(PM) 12 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 CS784(PM) 13 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) CS784(PM) 14 environment for g CS784(PM) 15 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) CS784(PM) 16 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 CS784(PM) 17 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) CS784(PM) 18 Specs of newref • Given: – (value-of exp ρ σ0) = (val, σ1), lc !∈ dom(σ1 ) • (value-of (newref-exp exp) ρ σ0) = ((ref-val lc), [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. CS784(PM) 19 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. CS784(PM) 20 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. CS784(PM) 21 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. CS784(PM) 22 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)))) CS784(PM) ; 23 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))) CS784(PM) 24 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))))) CS784(PM) 25 value-of-program (define value-of-program (lambda (pgm) (initialize-store!) (cases program pgm (a-program (exp1) (value-of exp1 (init-env)))))) CS784(PM) 26 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))))) CS784(PM) 27 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. CS784(PM) 28 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 CS784(PM) 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) 29 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]σ ) CS784(PM) 30 MUTABLE-PAIRS • A Language with Mutable Pairs • Reading Assignment CS784(PM) 31 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). CS784(PM) 32 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. CS784(PM) 33 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 CS784(PM) 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 34 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. CS784(PM) 35 call-by-ref implementation (define apply-procedure (lambda (proc1 val) (cases proc proc1 (procedure (var body savedenv) (value-of body (extend-env var val saved-env)))))) CS784(PM) (call-exp (rator rand) (let ((proc (expval->proc (value-of rator env))) (arg (value-ofoperand rand env))) (apply-procedure proc arg))) 36 value-of-operand (define value-of-operand (lambda (exp env) (cases expression exp (var-exp (var) (apply-env env var)) (else (newref (value-of exp env)))))) CS784(PM) 37 variable aliasing let b = 3 in let p = proc (x) proc(y) begin set x = 4; y end in ((p b) b) CS784(PM) • both x and y refer to the same location • Yields 4 • aliasing makes it difficult to understand programs. 38 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. CS784(PM) 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 39 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 CS784(PM) 40 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, and thereafter refers to the saved value. – an example of memoization. CS784(PM) 41 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 CS784(PM) (define-datatype thunk thunk? (a-thunk (exp1 expression?) (env environment?))) 42 value-of-operand (define value-of-operand (lambda (exp env) (cases expression exp (var-exp (var) (apply-env env var)) (else (newref (a-thunk exp env)))))) CS784(PM) 43 call by name design (var-exp (var) (let ((ref1 (apply-env env var))) (let ((w (deref ref1))) (if (expval? w) w (value-of-thunk w))))) CS784(PM) 44 value-of-thunk: Thunk→ExpVal (define value-of-thunk (lambda (th) (cases thunk th (a-thunk (exp1 saved-env) (value-of exp1 saved-env)))) CS784(PM) 45 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. CS784(PM) 46 memoization (var-exp (var) (let ((ref1 (apply-env env var))) (let ((w (deref ref1))) (if (expval? w) w (let ((val1 (value-of-thunk w))) (begin (setref! ref1 val1) val1)))))) CS784(PM) 47 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. CS784(PM) • 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. 48