CS221/321 Lecture 5, Oct 18, 2011 Continuing with SAEL. Contextual Reduction system for SAEL: Subst-by-Value version ---------------------- The only changes from SEA[CR] are the addition of the Let(v,C,e) clause in the definition of contexts, which forces the evaluation of the definiens for the Let can be reduced, and the Let reduction rule (3). Note that only Let exprs whose definiens are values are redexes. We also don't have a context form Let(v, Num(n), C), because we would reduce such a Let expression to its body immediately, i.e. we don't search for a redex in a Let body until after the Let has been reduced by rule (3). ---------------------------------------------------------------------- Fig 2.6: SAEL[CRv] By-Value Contextual Reductions for SAEL ---------------------------------------------------------------------- Contexts: C := [] | Plus(C, e) | Plus(Num(n), C) | Times(C, e) | Times(Num(n), C) | Let(v, C, e) Redex rules: (1) Plus(Num n1, Num n2) ↦ Num p where p = n1 + n2 (2) Times(Num n1, Num n2) ↦ Num p where p = n1 * n2 (3) Let(v, Num(n), e) ↦ [Num(n)/v]e Contextual reduction: (4) C[r] ↦ C[r'] where r a redex and r ↦ r' ---------------------------------------------------------------------- Note: There is no rule that will reduce a (free) variable, so evaluation of any expression containing a free variable will eventually get stuck when it encounters that variable. ---------------------------------------------------------------------- Example 2.1: Evaluate "let x = 2 + 3 in let y = x + 1 in x + y" in SAEL[CRv] "let x = 2 + 3 in let y = x + 1 in x + y" is represented as (omitting Num and Var constructors): e = Let(x, Bapp(Plus,2,3), Let(y, Bapp(Plus,x,1), Bapp(Plus,x,y))) = C1[Bapp(Plus,2,3)] where C1 = Let(x, [], Let(y, Bapp(Plus,x,1), Bapp(Plus,x,y))) ↦ C1[5] = Let(x, 5, Let(y, Bapp(Plus,x,1), Bapp(Plus,x,y))) = C2[Let(x, 5, Let(y, Bapp(Plus,x,1), Bapp(Plus,x,y)))] where C2 = [] ↦ C2[Let(y, Bapp(Plus,5,1), Bapp(Plus,5,y)))] = Let(y, Bapp(Plus,5,1), Bapp(Plus,5,y))) = C3[Bapp(Plus,5,1)] where C3 = Let(y, [], Bapp(Plus,5,y))) ↦ C3[6] = Let(y, 6, Bapp(Plus,5,y))) = C4[Let(y, 6, Bapp(Plus,5,y)))] where C4 = [] ↦ C4[Bapp(Plus,5,6)] = Bapp(Plus,5,6) = C5[Bapp(Plus,5,6)] where C5 = [] ↦ C5[11] = 11 ---------------------------------------------------------------------- ====================================================================== Subst-by-Name version ---------------------- Redex reduction rules for SAEL[CRn]: ---------------------------------------------------------------------- Fig 2.7: SAEL[CRn] By-Name Contextual Reductions for SAEL ---------------------------------------------------------------------- Contexts: C := [] | Plus(C, e) | Plus(Num(n), C) | Times(C, e) | Times(Num(n), C) Redex rules: (1) Plus(Num n1, Num n2) ↦ Num p where p = n1 + n2 (2) Times(Num n1, Num n2) ↦ Num p where p = n1 * n2 (3) Let(v, d, e) ↦ [d/v]e Contextual reduction: (4) C[r] ↦ C[r'] where r a redex and r ↦ r' ---------------------------------------------------------------------- Note: In this case, any Let expr is a redex. The contexts remain the same as for SAE, and we add a Let reduction rule (3) that applies to all Let exprs. ---------------------------------------------------------------------- Example 2.1: Evaluate "let x = 2 + 3 in let y = x + 1 in x + y" in SAEL[CRn] "let x = 2 + 3 in let y = x + 1 in x + y" is represented as (omitting Num and Var constructors): e = Let(x, Bapp(Plus,2,3), Let(y, Bapp(Plus,x,1), Bapp(Plus,x,y))) = Let(x, e1, Let(y, Bapp(Plus,x,1), Bapp(Plus,x,y))) where e1 = Bapp(Plus,2,3) = C1[Let(x, e1, Let(y, Bapp(Plus,x,1), Bapp(Plus,x,y))] where C1 = [] ↦ C1[Let(y, Bapp(Plus,e1,1), Bapp(Plus,e1,y))] = Let(y, Bapp(Plus,e1,1), Bapp(Plus,e1,y)) = C2[Let(y, Bapp(Plus,e1,1), Bapp(Plus,e1,y))] where C2 = [] ↦ C2[Bapp(Plus,e1,Bapp(Plus,e1,1))] = Bapp(Plus,e1,Bapp(Plus,e1,1)) = Bapp(Plus,Bapp(Plus,2,3),Bapp(Plus,e1,1)) -- expanding 1st e1 = C3[Bapp(Plus,2,3)] where C3 = Bapp(Plus,[],Bapp(Plus,e1,1)) ↦ C3[5] = Bapp(Plus,5,Bapp(Plus,e1,1)) = Bapp(Plus,5,Bapp(Plus,Bapp(Plus,2,3),1)) -- expanding e1 = C4[Bapp(Plus,2,3)] where C4 = Bapp(Plus,5,Bapp(Plus,[],1)) ↦ C4[5] = Bapp(Plus,5,Bapp(Plus,5,1)) = C5[Bapp(Plus,5,1)] where C5 = Bapp(Plus,5,[]) ↦ C5[6] = Bapp(Plus,5,6) = C6[Bapp(Plus,5,6)] where C6 = [] ↦ [11] = 11 ---------------------------------------------------------------------- ====================================================================== Exercise 2.2 : For SAEL[CRv], Prove that for any (nonvalue) expression e ∈ SAEL, there is a unique context C such that e = C[r], where r is a redex expression. Exercise 2.3 : Use both SAEL[CRv] and SAEL[CRn] to compute the value of the following expression: let x = 4 in let y = 2 + 3 in x * (let z = 2 + x in z * z) ====================================================================== CK Abstract Machine for SAEL ---------------------------- 1. Subst-by-Value version (SAEL[CKv]). ---------------------------------------------------------------------- Fig 2.8: SAEL[CKv] - CK-machine for SAEL Subst-by-Value ---------------------------------------------------------------------- Frames: f ::= Plus([], e2) | Plus*(Num(n), []) | Times([], e2) | Times*(Num(n), []) | Let*(v, [], e2) Stack/Context: k = nil | f :: k States: s = (e,k) Transition rules: (read n as Num(n)) (1) (Plus(e1,e2), k) => (e1, Plus([],e2)::k) (2) (Times(e1,e2), k) => (e1, Times([],e2)::k) (3) (n, Plus([],e2)::k) => (e2, Plus*(n,[])::k) (4) (n, Times([],e2)::k) => (e2, Times*(n,[])::k) (5) (n, Plus*(m,[])::k) => (p, k) where p = m+n (6) (n, Times*(m,[])::k) => (p, k) where p = m*n (7) (Let(v,e1,e2), k) => (e1, Let*(v, [], e2)::k) (8) (n, Let*(v,[],e2)::k) => ([n/v]e2, k) Initial states: (e, []) (where e is an expression to be evaluated) Final states: (Num(n), []) (where n ∈ Nat is the result value) ---------------------------------------------------------------------- Notes: As for SAEL[CRv], there is one new Let frame corresponding to the Let context clause, and two new Let transition rules. The first moves the focus to the definiens of a let, while the other performs the Let reduction once the definiens has been evaluated. 2. Subst-by-Name version (SAEL[CKn]). ---------------------------------------------------------------------- Fig 2.9: SAEL[CKn] - CK-machine for SAEL Subst-by-Name ---------------------------------------------------------------------- Frames: f ::= Plus([],e2) | Plus*(Num(n), []) | Times([], e2) | Times*(Num(n), []) Stack/Context: k = nil | f :: k States: s = (e,k) Transition rules: (read n as Num(n)) (1) (Plus(e1,e2), k) => (e1, Plus([],e2)::k) (2) (Times(e1,e2), k) => (e1, Times([],e2)::k) (3) (n, Plus([],e2)::k) => (e2, Plus*(n,[])::k) (4) (n, Times([],e2)::k) => (e2, Times*(n,[])::k) (5) (n, Plus*(m,[])::k) => (p, k) where p = m+n (6) (n, Times*(m,[])::k) => (p, k) where p = m*n (7) (Let(v,e1,e2), k) => ([e1/v]e2, k) Initial states: (e, []) (where e is an expression to be evaluated) Final states: (Num(n), []) (where n ∈ Nat is the result value) ---------------------------------------------------------------------- Notes: As for SAEL[CRn], there is no new frame for Let, and only one new transition rule, for the immediate reduction of Let exprs. A Let is a redex before any evaluation of its definiens. ====================================================================== Environments (lazy substitutions) ---------------------------------- The problem with all the dynamic semantics for SEAL ([BSv,n], [SSv,n], [CRv,n], and [CKv,n]) is that substitution is not a simple, fixed-cost operation. Its cost is proportional to the size of the object expression to which the substitution is applied (an possibly to the size of the substituted expression for substitution-by-name). We want to avoid this potentially costly operation as a "primitive" of evaluation. What if instead of actually performing the substitution [e/x], we just remember it, and only use it if and when we encounter an occurrence of x? How do we remember such saved substitutions? In an _environment_. We'll start with the subst-by-value case. Defn 4.1: An environment E is a finite mapping from variables to values (numbers, or value expressions). Since an environment is a mapping or function, to look up a variable in an environment, we just need to apply the environment to the variable: E(x). This will return the value associated with the variable in that environment. We could define a lookup operation by: look(x,E) = E(x) To add a new binding to an existing environment E, we can use the bind function defined as bind(x,v,E) = λy. if y=x then v else E(y) which produces a new environment function as a modification/extension of E. Finally, the initial, empty environment would be emptyEnv = λy. undefined Another common representation of environments uses an "association list" (alist) data structure, which is a list of ordered pairs consisting of a variable and the value that is "bound" to the variable: env = (variable * value) list emptyEnv = [] bind(x,v,E) = (x,v)::E look(x,nil) = undefined look(x,(y,v)::E) = if x=y then v else look(x,E) E.g. [(x,3)], [(y,4), (x,3)] Whether environments are represented as functions or association lists, it is possible for a new binding to override or "shadow" an old binding. For example: E = bind(x,3,emptyEnv) look(x,E) = 3 E' = bind(x,4,E) look(x,E') = 4 here E and E' correspond to the finite functions {(x,3)} and {(x,4)}, whichever representation is chosen. In the alist representation, they are E = [(x,3)] and E' = [(x,4), (x,3)]; the look function searches for a binding for x from left to right, and finds 4 in E'. Viewing env as an abstract type representing finite mappings from variables to values, it has an interface consisting of emptyEnv: env bind : variable * value * env -> env look : variable * env -> value The appropriate definition of "value" stored in environments will vary according to circumstances, and as we will see, it may or may not be the same as the values computed by expressions. In the examples below, we will use the alist representation when presenting an environment, since it is more compact. ---------------------------------------------------------------------- Fig 2.10: SAEL[BSEv] - SAEL Big-Step binding-by-value semantics with environments ---------------------------------------------------------------------- expr = SAEL expressions value = Nat env = "variable -> Nat" Eval : expr * env -> Nat (1) Eval(Num(n),E) = n (2) Eval(Var(x),E) = look(x,E) (3) Eval(Bapp(bop, e1,e2),E) = prim(bop, Eval(e1,E), Eval(e2,E)) (4) Eval(Let(x,e1,e2), E) = Eval(e2, bind(x,Eval(e1,E),E)) ---------------------------------------------------------------------- So we have eliminated substitution, at the cost of having to carry an environment parameter through the computation. and also the issue of free variable capture (although that only affects substitute-by-name semantics - why?). SAEL[BSEn] - SAEL Big-Step bind-by-name semantics with environments Now we turn to evaluation for a bind-by-name version of SAEL. We will start with a naive version: ---------------------------------------------------------------- value = expr (values bound in environments) env = "variable -> expr" Eval : expr * env -> Nat Rules (1) Eval(Num(n), E) = n (2) Eval(Var(x), E) = Eval(e, E) where look(x,E) = e (3) Eval(Bapp(bop,e1,e2), E) = prim(bop, Eval(e1,E), Eval(e2,E)) (4) Eval(Let(x,e1,e2), E) = Eval(e2, bind(x,e1,E)) ---------------------------------------------------------------- Now consider the example e = let x = 3 in let y = x+3 in let x = 5 in x+y Eval(let x = 3 in let y = x+3 in let x = 5 in x+y, []) = Eval(let y = x+3 in let x = 5 in x+y, E1]) = (E1 = [(x,3)]) Eval(let x = 5 in x+y, E2) = (E2 = [(y,x+3), (x,3)]) Eval(x+y, E3) = (E3 = [(x,5), (y,x+3), (x,3)]) Eval(x, E3) + Eval(y, E3) (1) Eval(x, E3) = 5 (2) Eval(y, E3) = x+3 Now we have a problem. The evaluation of subexpression y returns an expression, not a number, and that expression contains a free occurrence of the variable "x". What are we supposed to do with this? If we evaluate it wrt the most recent environment E3 again, we get Eval(x+3,E3) = Eval(x,E3) + eval(3,E3) = 5+3 = 8 and then the final answer will be 5+8 = 13. But if we evaluate e by the (by-value) substitution semantics, we would get e = let x = 3 in let y = x+3 in let x = 5 in x+y ↦ let y = 3+3 in let x = 5 in x+y ↦ let y = 6 in let x = 5 in x+y ↦ let x = 5 in x+6 ↦ 5+6 ↦ 11 So we got the wrong answer using Eval. We would have gotten the right answer if we had evaluated x+3 in E2 or E1. But how do we know in general when to switch environments and which environment to switch to? The solution is that when we bind an unevaluated expression in the environment, we have to "close" it with respect to its proper environment -- the environment that is current when we process that let binding. Closing an expression means constructing a "closure", , which consists of an expression e together with an environment E that should be used when we eventually need to evaluate e. E should bind any free variables that occur in e. Terminology: Another term that has been used for these "expression closures" of the form is "thunk". This terminology dates back to implementations of Algol 60 in the 1960s. Now we can give the correct big-step semantics for bind-by-name with environments, using closures as a new kind of "value". ---------------------------------------------------------------------- Fig 2.11: SAEL[BSEn] ---------------------------------------------------------------------- env = variable -> closure closure = expr * env look : var * env -> closure look(x,E) = E(x) bind : var * expr * env -> env bind(x,e,E) = λy. if y=x then else E(y) Eval : expr * env -> Nat Rules (1) Eval(Num(n), E) = n (2) Eval(Var(x), E) = Eval(e,E') where look(x,E) = (3) Eval(Bapp(bop,e1,e2), E) = prim(bop, Eval(e1,E), Eval(e2,E)) (4) Eval(Let(x,e1,e2), E) = Eval(e2, bind(x,e1,E)) ---------------------------------------------------------------------- Example: Let's see how the example is evaluated in this new version: Eval(let x = 3 in let y = x+3 in let x = 5 in x+y, []) = Eval(let y = x+3 in let x = 5 in x+y, E1) = (E1 = [(x,3)]) Eval(let x = 5 in x+y, E2) = (E2 = [(y,), (x,3)]) Eval(x+y, E3) = (E3 = [(x,5), (y,), (x,3)]) (1) Eval(x, E3) = 5 (2) Eval(y, E3) = Eval(x+3, E1) because look(y,E3) = = Eval(x,E1) + Eval(3,E1) = 3 + 3 = 6 So Eval(x+y, E3) = Eval(x,E3) + Eval(y,E3) = 5 + 6 = 11 And we have the correct answer. Note: In this example, we use a shortcut by not creating closures for Num expressions, where the closure environment would not be relevant, i.e. we write E1 as [(x,3)] rather than [(x,)]. ====================================================================== Exercise 2.4: Write an SML program that implements SAEL[BSn]. ====================================================================== ML implementations of SAEL[BSEv] and SAEL[BSEn]. ---------------------------------------------------------------------- Program 2.2. "by-value" environment passing evaluator for SAEL ---------------------------------------------------------------------- See prog_2_2.sml. ---------------------------------------------------------------------- ---------------------------------------------------------------------- Program 2.3. "by-name" environment passing evaluator for SAEL ---------------------------------------------------------------------- See prog_2_3.sml ---------------------------------------------------------------------- Can we define an environment based version of the substitution based SAEL[SSv] (Figure 2.2)? The obvious way to introduce environments is to modify the ↦ relation so that it relates expression-environment closures rather than plain expressions. Here are some natural looking rules: (var) i.e. reduce a variable in an environment to its binding in that environment, leaving the environment constant. (let) i.e. reduce a let-redex to its body in the original environment extended by the let binding. The problem is, how do we add environments to the Bapp search rules. One plausible version is: ------------------------------------------------ -------------------------------------------------------- But if we use these rules to evaluate let x = 2 in (let x = 3 in x) + x something goes wrong. The last x in the body of the outer let will be evaluated in the environment [(x,3),(x,2)], which causes it to evaluate to 3 instead of 2. The problem is that there is a rule to add a binding to the environment when entering a binding scope, but there is no rule for removing the binding when we leave its scope. There is no simple, obvious fix for this problem. We just don't try to provide an environment based variant of the SAEL[SSv] semantics. The problem doesn't go away if we look at the by-name SAEL[SSn] either. Things would get more complicated since expression closures instead of value expression would be bound in the environment. The conclusion is that the "plumbing" for passing environment around through a computation, respecting scoping rules, is straightforward for a big-step evaluator, but seems very hard to add to a small-step transition semantics. Blending environments into context-reduction semantics also seems impractical. Because each CR reduction involves global factoring analysis of the whole term, it is not clear how to deal with the multiple environments (for different binding scopes) that are needed at different subexpressions. -------------------------------- The CEK environment abstract machine: ---------------------------------------------------------------------- Fig 2.12: SAEL[CEKv] - CEK-machine for SAEL with bind-by-value ---------------------------------------------------------------------- Frames: f ::= Bapp(bop, [], e2, E) | Bapp*(bop, n, []) | Let(v, [], e2, E) Stack/Context: k = nil | f :: k Environments: env = (variable * Nat) list emptyEnv : env = [] look : variable * env -> Nat look (v, []) = undef look (v, ((v',n):env)) = if v=v' then n else look(v, env) bind : variable * Nat * env -> env bind (v, n, env) = (v,n)::env States: s = (e,E,k) Initial states: (e, emptyEnv, []) (where e is a closed expression to be evaluated, and [] is the empty stack) Final states: (Num(n), env, []) (where n ∈ Nat is the result value, env arbitrary) Transition rules: (1) (Bapp(bop,e1,e2), E, k) => (e1, E, Bapp(bop,[],e2,E)::k) (2) (Num(n), E, Bapp(bop,[],e2,E')::k) => (e2, E', Bapp*(bop,n,[])::k) (3) (Num(n), E, Bapp*(bop,m,[])::k) => (Num(p), E, k) where p = prim(bop,m,n) (4) (Let(v,e1,e2), E, k) => (e1, E, Let(v, [], e2, E)::k) (5) (Num(n), E, Let(v,[],e2,E')::k) => (e2, bind(v,n,E'), k) (6) (Var(x), E, k) => (Num(look(x,E)), E, k) ---------------------------------------------------------------------- Notes: We have places in the Bapp and Let frames to store the appropriate "closure" environment to be used when evaluating the pending expression e2 (i.e. the second arg of the Bapp, or the body of the let expr). Restoring these stored environments in rules (2) and (5) is how we arrange to pop back to the appropriate environment when leaving scopes of let bindings. In rules (3) and (6), the environment component E is retained in the result state, though it will never be used. We could replace E with emptyEnv on the right-hand-side of those rules. Because of this treatment of the environment in (3) and (6), the final states may retain an irrelevant, nonempty environment component. We call (1) and (4) "analysis" transitions -- they break down the current expression. We call (2) a "shift" transition -- it shifts attention to the second argument of a Bapp once the first argument is evaluated. (3), (5), and (6) are "reduce" transitions -- they eliminate a redex (viewing an applied variable occurrence as a redex). ---------------------------------------------------------------------- Program 2.4. Implementation of SEAL[CEKv] machine ---------------------------------------------------------------------- See prog_2_4.sml ---------------------------------------------------------------------- ---------------------------------------------------------------------- Fig 2.13: SAEL[CEKn] - CEK-machine for SAEL with bind-by-value ---------------------------------------------------------------------- Frames: f ::= Bapp(bop, [], e2, E) | Bapp*(bop, n, []) | Let(v, [], e2, E) Stack/Context: k = nil | f :: k States: s = (e,E,k) Closures: c = Initial states: (e, emptyEnv, []) (where e is a closed expression to be evaluated, and [] is the empty stack) Final states: (Num(n), env, []) (where n ∈ Nat is the result value, env arbitrary) Environments: env = (variable * closure) list emptyEnv : env = [] look : variable * env -> Nat look (v, []) = undef look (v, ((v',c):E)) = if v=v' then c else look(v, E) bind : variable * expr * env -> env bind (v,e,E) = (v,)::E Transition rules: (1) (Bapp(bop,e1,e2), E, k) => (e1, E, Bapp(bop,[],e2,E)::k) (2) (Num(n), E, Bapp(bop,[],e2,E')::k) => (e2, E', Bapp*(bop,n,[])::k) (3) (Num(n), E, Bapp*(bop,m,[])::k) => (Num(p), E, k) where p = prim(bop,m,n) (4) (Let(v,e1,e2), E, k) => (e2, bind(v,e1,E), k) (5) (Var(x), E, k) => (e, E', k) where = look(x,E) ---------------------------------------------------------------------- Notes: Values bound to variables in environments are closures , even in the cases where e is a number expression Num(e) where the environment is not needed. The final values are still number expressions.