CS221/321 Lecture 5, Oct 14, 2010 Continuing with SAEL. Contextual Reduction system for SAEL: Subst-by-Value version ---------------------- Redex reduction rules for SAEL[CRv]: ---------------------------------------------------------------------- Figure 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: The only changes 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). Only Let expr with value definiens are redexes. ====================================================================== Subst-by-Name version ---------------------- Redex reduction rules for SAEL[CRn]: ---------------------------------------------------------------------- Figure 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. ====================================================================== Homework 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. Homework 2.3 : Use both SAEL[CRv] and SAEL[CRn] to compute the value of the following expression: let x = 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]). ---------------------------------------------------------------------- Figure 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]). ---------------------------------------------------------------------- Figure 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 occurence 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 associtate with the variable in that environment. We could define a lookup operation by: look(x,E) = E(x) SAEL[BSEv] - SAEL Big-Step semantics with environments (by-Value) (1) Eval(Num(n),E) = n (2) Eval(Var(x),E) = look(x,E) (i.e. E(x)) (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)) where bind(x,n,E) = λy. if y=x then n else E(y) So we have eliminated substitution, and also the issue of free variable capture (although that only affects substitute-by-name semantics - why?). ---------------------------------------------------------------------- SAEL[BSEn] - SAEL Big-Step semantics with environments (by-Name) [This section still under construction,] We will start with a naive version: env = variable -> expr look : var * env -> expr look(x,E) = E(x) bind : var * expr * env -> env bind(x,e,E) = λy. if y=x then e 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) = 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, [(x,3)]) = E1 = [(x,3)] Eval(let x = 5 in x+y, [(y,x+3),(x,3)]) = E2 = [(y,x+3),(x,3)] Eval(x+y, [(x,5),(y,x+3),(x,3)]) = E3 = [(x,5),(y,x+3),(x,3)] Eval(x, [(x,5),(y,x+3),(x,3)]) + Eval(y, [(x,5),(y,x+3),(x,3)]) (1) Eval(x, [(x,5),(y,x+3),(x,3)]) = 5 (2) Eval(y, [(x,5),(y,x+3),(x,3)]) = 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, 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 get the wrong answer. 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 deal with 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 further evaluating 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. ---------------------------------------------------------------------- Figure 2.10: 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)) ---------------------------------------------------------------------- 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, [(x,3)]) = E1 = [(x,3)] Eval(let x = 5 in x+y, [(y,),(x,3)]) = E2 = [(y,),(x,3)] Eval(x+y, [(x,5),(y,),(x,3)]) = E3 = [(x,5),(y,),(x,3)] (1) Eval(x, E3) = 5 (2) Eval(y, E3) = Eval(x+3, E1) = 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. ====================================================================== Homework 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 code/prog_2_2.sml ---------------------------------------------------------------------- ---------------------------------------------------------------------- Program 2.3. "by-name" environment passing evaluator for SAEL ---------------------------------------------------------------------- See code/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: ---------------------------------------------------------------------- Figure 2.7: SAEL[CEKv] - CEK-machine for SAEL with by-Value let ---------------------------------------------------------------------- Frames: f ::= Bapp(pop, [], e2, E) | Bapp*(n, []) | Let(v, [], e2, E) Stack/Context: k = nil | f :: k Environments: env look : variable * env -> Nat bind : variable * Nat * env -> env States: s = (e,E,k) Initial states: (e, emptyEnv, []) (where e is an expression to be evaluated, and [] is the empty stack) Final states: (Num(n), emptyEnv, []) (where n ∈ Nat is the result value) 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), 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) ---------------------------------------------------------------------- Note that 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. 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. CEKv machine for SAEL ---------------------------------------------------------------------- See code/prog_2_4.sml ---------------------------------------------------------------------- ---------------------------------------------------------------------- Figure 2.7: SAEL[CEKv] - CEK-machine for SAEL with by-Value let ---------------------------------------------------------------------- Frames: f ::= Bapp(pop, [], e2, E) | Bapp*(n, []) | Let(v, [], e2, E) Stack/Context: k = nil | f :: k States: s = (e,E,k) Environments: env look : variable * env -> closures 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), 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) Initial states: (e, emptyEnv, []) (where e is an expression to be evaluated, and [] is the empty stack) Final states: (Num(n), emptyEnv, []) (where n ∈ Nat is the result value) ---------------------------------------------------------------------- --------------------------------------------------------------------------- Question: How would SAEL[CEKv] need to be modified to model a by-Name let?