CS221/321 Lecture 9, Nov 11, 2010 Type Soundness for TFun ----------------------- Type soundness for TFun is expressed through the Preservation and Progress Lemmas. Lemma 4.12 [Preservation]: ⊦ e : τ & e ↦ e' ==> ⊦ e' : τ Lemma 4.13 [Progress]: ⊦ e : τ ==> e is a value or ∃e'. e ↦ e'. Together these insure that (Small-Step) evaluation of a well-typed expression will never "get stuck" in a non-value expression capable of no transition. They also ensure that if ⊦ e: τ and e ↦! v, then the final value expression v has the same type τ. Proof of Lemma 4.12, Preservation. ---------------------------------- We will prove this by induction on the derivation of the transition e ↦ e'. More precisely, we will prove ∀d. P(d) where P(d) == ∀τ. ⊦ source(d) : τ => ⊦ target(d) : τ Here, as usual, source(d) and target(d) are the expressions e and e' where the final judgement of d is e ↦ e'. Derivations are constructed using rule constructors corresponding to the 9 rules in Fig. 4.5 (TFun[SSv]): d ::= Rule1(bop,n1,n2) source = App(App(bop,n1),n2) target = expOf(prim(bop,n1,n2)) | Rule2(v1,v2) where v1 is of the form Fun(x,τ,e) source = App(v1,v2) target = [v2/x]e | Rule3(e2,d1) source = App(source(d1),e2) target = App(target(d1),e2) | Rule4(v1,d2) source = App(v1,source(d2)) target = App(v1,target(d2)) | Rule5(x,τ,v1,e2) source = Let(x,τ,v1,e2) target = [v1/x]e2 | Rule6(x,τ,e2,d1) source = Let(x,τ,source(d1),e2) target = Let(x,τ,target(d1),e2) | Rule7(e2,e3,d1) source = If(source(d1),e2,e3) source = If(target(d1),e2,e3) | Rule8(e2,e3) source = IF(True,e2,e3) source = e2 | Rule9(e2,e3) source = If(False,e2,e3) source = e3 (Here I've combined the specification of the derivation constructors and their parameters, and the definitions of the source and target functions over derivations. As usual, we omit the Const and Num constructors, because it is obvious where to insert them where needed.) Rule1, Rule2, Rule5, Rule8, and Rule9 will give base cases, while the rest will be inductive cases. Base: d = Rule1(bop,n1,n2). Then e = source(d) = App(App(bop,n1),n2) e' = target(d) = expOf(prim(bop,n1,n2)) There are two subcases to consider, depending on whether bop is an arithmetic or a relational operator. If bop is an arithmetic primitive op (Plus,Times,Minus), then Σ(bop) = Int → Int → Int. We will also assume that for such arithmetic primites, prim(bop,n1,n2) returns a number n, and expOf(n) will be Const(Num(n)). We have the following derivation of a typing for e: Σ(bop) = Int → Int → Int Σ(n1) : Int ---------------------------- ------------- ⊦ bop : Int → Int → Int ⊦ n1 : Int Σ(n2) : Int ---------------------------------------------- -------------- ⊦ App(bop,n1) : Int → Int ⊦ n2 : Int --------------------------------------------------------- ⊦ App(App(bop,n1),n2) : Int Also, by Lemma 4.11, if ⊦ e: τ is any deriveable typing of e, then τ must be Int. So all we need to show is that ⊦ n : Int, which is immediate by Fig. 4.3(1) and the fact that Σ(n) = Int. The case where bop is a relational operator is similar, except that the type of bop will be Σ(bop) = Int → Int → Bool and the result of prim(bop,n1,n2) will be a boolean value b, and Σ(b) = Bool. Base: d = Rule2(v1,v2), where v1 = Fun(x,τ,e) for some x, τ, and e. Then e = source(d) = App(v1,v2) e' = target = [v2/x]e and we assume that for some τ, (1) ⊦ e : τ, i.e. ⊦ App(v1,v2) : τ (2) ∃τ'. ⊦ v1 : τ' → τ and (3) ⊦ v2 : τ' from (1) by Inversion (Lemma 4.8(3)). (4) x:τ' ⊦ e : τ from (2) by Inversion (Lemma 4.8(5)), where v1 = Fun(x,τ,e) (5) ⊦ [v2/x]e : τ by Substituion Lemma (Lemma 4.9) (6) ⊦ e' : τ since e' = [v1/x]e. [X] Ind Case: d = Rule3(e2,d1). Then e = source(d) = App(source(d1),e2) = App(e1,e2) e' = target(d) = App(target(d1),e2) = App(e1',e2) where e1 = source(d1) and e1' = target(d1) IH: P(d1), i.e. ∀τ1. ⊦ e1: τ1 => ⊦ e1' : τ1 Now let us assume (1) ⊦ e : τ. Then: (2) ∃τ2. ⊦ e1 : τ2 → τ and (3) ⊦ e2 : τ2 from (1) by Inversion (Lemma 4.8(6)) (4) ⊦ e1' : τ2 → τ by (2) and the IH (5) ⊦ App(e1',e2) : τ by Fig. 4.3(6). (6) ⊦ e' : τ since e' = App(e1',e2). [X] Ind Case: d = Rule4(v1,d2). Then e = source(d) = App(v1,source(d2)) = App(v1,e2) e' = target(d) = App(v1,target(d2)) = App(v1,e2') where e2 = source(d2) and e2' = target(d2). IH: P(d2), i.e. ∀τ1. ⊦ e2: τ1 => ⊦ e2' : τ1. (1) ⊦ e : τ (hypothesis) (2) ∃τ2. ⊦ v1 : τ2 → τ and (3) ⊦ e2 : τ2 by (1) and Inversion (Lemma 4.8(6)) (4) ⊦ e2' : τ2 by (3) and the IH (5) ⊦ App(v1,e2') : τ by Fig. 4.3(6). (6) ⊦ e' : τ since e' = App(v1,e2'). [X] Base Case: d = Rule5(x,τ1,v1,e2). Then e = source(d) = Let(x,τ1,v1,e2) e' = target(d) = [v1/x]e2 (1) ⊦ e : τ (hypothesis) (2) [x:τ1] ⊦ e2: τ and (3) ⊦ v1 : τ1 by (1) and Inversion (Lemma 4.8(4)) (4) ⊦ [v1/x]e2 : τ by (2), (3) and the Substitution Lemma (5) ⊦ e': τ by defn e' [X] Ind Case: d = Rule6(x,τ1,e2,d1) Then e = source(d) = Let(x,τ1,e1,e2) e' = target(d) = Let(x,τ1,e1',e2) where e1 = source(d1) and e1' = target(d1). IH: P(d1), i.e. ∀τ0. ⊦ e1: τ0 => ⊦ e1' : τ0. (1) ⊦ e : τ (hypothesis) (2) [x:τ1] ⊦ e2: τ and (3) ⊦ e1 : τ1 by (1) and Inversion (Lemma 4.8(4)) (4) ⊦ e1' : τ1 by IH and (3) (5) ⊦ Let(x,τ1,e1',e2) : τ by Fig. 4.3(4) (6) ⊦ e' : τ by defn of e'. [X] Ind Case: d = Rule7(e2,e3,d1) Then e = source(d) = If(e1,e2,e3) e' = target(d) = If(e1',e2,e3) where e1 = source(d1) and e1' = target(d1). IH: P(d1), i.e. ∀τ0. ⊦ e1: τ0 => ⊦ e1' : τ0. (1) ⊦ e : τ (hypothesis) (2) ⊦ e1: Bool and (3) ⊦ e2: τ and (4) ⊦ e3: τ by (1) and Inversion (Lemma 4.8(3)) (5) ⊦ e1': Bool by IH and (2) (6) ⊦ If(e1',e2,e3) : τ by (5), (3), and (4) and Fig 4.3(3). (7) ⊦ e' : τ by defn of e' [X] Base Case: d = Rule8(e2,e3). Then e = source(d) = If(True,e2,e3) e' = target(d) = e2 (1) ⊦ e : τ (hypothesis) (2) ⊦ True: Bool and (3) ⊦ e2: τ and (4) ⊦ e3: τ by (1) and Inversion (Lemma 4.8(3)) (5) ⊦ e' : τ by (3) and defn of e' [X] Base Case: d = Rule9(e2,e3). Then e = source(d) = If(False,e2,e3) e' = target(d) = e3 (1) ⊦ e : τ (hypothesis) (2) ⊦ False: Bool and (3) ⊦ e2: τ and (4) ⊦ e3: τ by (1) and Inversion (Lemma 4.8(3)) (5) ⊦ e' : τ by (4) and defn of e' [XX] ============================================================================= Homework 5.1: Give the proofs of the remaining 6 cases for the Preservation Lemma (Rule4 through Rule9). ============================================================================= Proof of the Lemma 4.13: Progress. ---------------------------------- We prove this by induction on the derivation of ⊦ e : τ. We break down into cases according to the root rule (from Fig 4.3) used in the derivation. Base Case: ⊦ e : τ by Rule (1). Then e = c for some constant, and all constant expressions are values. [X] Base Case: ⊦ e : τ by Rule (2). This cannot happen -- no variable has a type in the empty context. Base Case: ⊦ e : τ by Rule (5). Then e = Fun(x,τ,e'), and e is a value. [X] Ind Case: ⊦ e : τ by Rule (3). Then (1) e = If(e1,e2,e3), for some e1,e2,e3 by Case Hyp. (2) ⊦ e1 : Bool, and (3) ⊦ e2 : τ, and (4) ⊦ e3 : τ by (1) and Lemma Hyp. and Inversion (Lemma 4.8(3)) (5) IH: e1 is a value, or ∃e1'. e1 ↦ e1' Case 1: e1 is a value (6) e1 = True or e1 = False, by (2) and Cannonical Forms (Lemma 4.10(2)) Case 1.1: e1 = True (7) e ↦ e2 by TFun[SSv](8) [X] Case 1.2: e1 = False (8) e ↦ e3 by TFun[SSv](9) [X] Case 2: e1 ↦ e1' for some e1' (9) e = If(e1,e2,e3) ↦ If(e1',e2,e3) by TFun[SSv](7). [XX] Ind Case: ⊦ e : τ by Rule (4). [Homework 5.2.] Then (1) e = Let(x,τ1,e1,e2) by Case Hyp. (2) ⊦ e1: τ1 and (3) [x:τ1] ⊦ e2 : τ by (1) and Lemma Hyp. and Inversion Lemma (4.8(4)) (IH) e1 a value or ∃e1'. e1 ↦ e1'. Case 1: e1 is a value. (4) e ↦ [e1/x]e2 by Fig 4.5(5) [X] Case 2: ∃e1'. e1 ↦ e1'. (5) e ↦ Let(x,τ1,e1',e2) by Fig 4.5(6). [X] Ind Case: ⊦ e : τ by Rule (6). Then (1) e = App(e1,e2) for some e1, e2 by Case Hyp. (2) ⊦ e1 : τ1 → τ for some type τ1, and (3) ⊦ e2 : τ1 by (1) and Lemma Hyp. and Inversion (Lemma 4.8(6)) (4) IH1: e1 is a value or ∃e1'. e1 ↦ e1' (5) IH2: e2 is a value or ∃e2'. e2 ↦ e2' Case 1: e1 ↦ e1' for some e1' (6) e = App(e1,e2) ↦ App(e1',e2) by TFun[SSv](3) Case 2: e1 is a value Case 2.1: e2 ↦ e2' (7) e = App(e1,e2) ↦ App(e1,e2') by TFun[SSv](4) [X] Case 2.2: e2 a value (6) e1 = Fun(x,τ1,e), or e1 = primop, or e1 = App(primop, v) (where v a value) by Cannonical Forms (Lemma 4.10(3)) Case 2.2.1: e1 = Fun(x,τ1,e) (7) e = App(Fun(x,τ1,e),e2) ↦ [e2/x]e by TFun[SSv](2) [X] Case 2.2.2: e1 = primop (8) e = App(primop, e2) where e2 a value: e is a value [X] Case 2.2.3: e1 = App(primop,v) (9) ⊦ primop : τ2 → (τ1 → τ) and ⊦ v : τ2 by Inversion of (2) (10) ⊦ primop : Int → (Int → τ) where τ = Int or τ = Bool, by the known signature typing of primops. (11) τ2 = τ1 = Int (12) v = n for some number n (actually Const(Num(n)), by (9), (11), and Cannonical Forms (Lemma 4.10(1)) (13) ⊦ e2 : Int by (3), (11) (14) e2 = m for some number m (actually Const(Num(m)), by Case 2 hyp, (13), and Cannonical Forms (Lemma 4.10(1)) (15) e = App(App(primop,n),m) ↦ prim(primop,n,m) by TFun[SSv](1). [XX] ====================================================================== Recursive functions in TFun TFun does not support typing expressions like (x x), which are used in both the CBN and CBV versions of the Y combinator. So in TFun we can't view recursive functions as a derived construct as we could in Fun. There are sevaral ways to reintroduce support for defining recursive functions in a (simply) typed language like TFun. (1) Y as a special primitive operator. (2) A new binding construct: μf.e. (3) A new letrec binding construct. ------------------------------ (1) Y as a primitive operator. Dynamic Semantics: The dynamic semantics would add one new reduction rule for the syntactic form Y e (which would be a kind of speciallized application form). In the CBN language, this would be (added to Fig 4.6): (8) Y e ↦ e (Y e) (CBN Y) while in the CBV language, it would be (added to Fig 4.5): (10) Y v ↦ v (λy.(Y v) y) (CBV Y) In the CBV case, we also need the usual search rule to evaluate the "argument" of Y: e ↦ e' ------------- Y e ↦ Y e' In either case, the "argument" of Y represents a generator function for the recursive function being introduced. Note that in both cases, the reduction rules above correspond to the behavior of the defined Y combinators when their applications are reduced in Fun. Typing: Because of the contractum forms in these rules, it is clear that e (for CBN Y) and v (for CBV Y) must be functions. In addition, in the CBV case, (Y v) must also be a function. Thus the typing rule (for either form of Y) is (added to Fig 4.3): Γ ⊦ e : (τ → τ1)→ (τ → τ1) (7) --------------------------- Γ ⊦ Y e : τ → τ1 With this Y primitive, we can once again define the factorial function by let fact = Y Fact where Fact = λg.λx. if x = 0 then 1 else x * fact(x - 1). We have, for example (in CBV): fact 2 = (Y Fact) 2 ↦ Fact (λy.(Y Fact)y) 2 ↦ (λx.if x = 0 then 1 else x * fact(x - 1)) 2 ↦ if 2 = 0 then 1 else 2 * fact(2 - 1) ↦ if false then 1 else 2 * fact(2 - 1) ↦ 2 * fact(2 - 1) ↦ 2 * fact 1 ... almost as before, minus an intermediate step in reducing (Y Fact) using the former definition of Y. --------------------------------- (2) A new binding operator: μf.e We introduce a new form of expression that directly represents recursive function values, using a new binding operator μ. Syntax: e ::= ... | μf:τ→τ1.e Dynamic Semantics: For both CBV and CBN add (Fig 4.5 (10) and Fig 4.6 (8)): (10) μf:τ.e ↦ [(μf:τ.e)/f]e Normally e will be a λ-abstraction (hence a value). Typing: Add the following rule to Fig 4.3: Γ[f: τ → τ1] ⊦ e : (τ → τ1) (7) ---------------------------- Γ ⊦ μf:τ → τ1. e : τ → τ1 Example: Define the factorial by fact = μf:Int→Int.(λx:Int.if x = 0 then 1 else x * f(x-1)) = μf:Int→Int.Fact(f) where Fact(f) = λx:Int.if x = 0 then 1 else x * f(x-1) fact 2 ↦ ([fact/f]Fact(f)) 2 = Fact(fact) 2 ↦ (λx:Int.if x = 0 then 1 else x * fact(x-1)) 2 ↦ if 2 = 0 then 1 else 2 * fact(2-1) ↦* 2 * fact 1 = 2 * (μf:Int→Int.Fact(f)) 1 ↦ ... Note. The recursive function expressions of the form fun f(x:τ1):τ2 is e in Harper's MinML language (Harper, Chapter 9, page 49) correspond to the μ-expression μf:τ1→ τ2.λx:τ1.e. The single (CBV) reduction rule (9.16) [page 55] given in Harper will be performed by a sequence of a μ-reduction by rule (10) above to bind f, followed by a β-reduction to bind x. ------------------------ (3) letrec declarations. The third option for supporting typed recursive functions is the letrec declaration. (1) letrec fact: Int→Int = λx:Int.if x=0 then 1 else x*fact(x-1) in fact 2 The general form is (2) letrec f:τ1→τ2 = λx:τ1.e1 in e2 the defined variable f must have a function type explicitly specified, and the definiens must be a λ-abstraction (i.e. a Fun expression). The abstract syntax for letrec is therefore: Syntax: e ::= ... | Letrec(x1,x2,τ1,τ2,e1,e2) where Letrec(x1,x2,τ1,τ2,e1,e2) represents letrec x1: τ1→τ2 = λx2:τ1.e1 in e2 What about a small-step transition rule for letrec? This is somewhat surprisingly difficult to define. For instance, we could reduce the letrec expression by replacing each recursive call of the defined function in its definiens with a new letrec, while changing the original letrec to a simple let. For (1), this would yield: (3) let fact: Int→Int = λx:Int.if x=0 then 1 else x * letrec fact: Int→Int = λx:Int.if x=0 then 1 else x * fact(x-1) in fact(x-1) in fact 2 which reduces to (λx:Int.if x=0 then 1 else x * letrec fact: Int→Int = λx:Int.if x=0 then 1 else x * fact(x-1) in fact(x-1)) 2 which β-reduces to if 2=0 then 1 else 2 * letrec fact: Int→Int = λx:Int.if x=0 then 1 else x * fact(x-1) in fact(2-1) which reduces in two steps to 2 * (letrec fact: Int→Int = λx:Int.if x=0 then 1 else x * fact(x-1) in fact(2-1)) and so on. This will work, but it is a rather messy reduction rule, so rather than implement such a direct reduction, it is more common to define letrec by viewing it as "syntactic sugar" that can be expanded into a form using the Y primitive or a μ-expression. The translation using Y is: letrec x1: τ1→τ2 = λx2:τ1.e1 in e2 ===> let x1: τ1→τ2 = Y (λx1:τ1→τ2.λx2:τ1.e1) in e2 and the translation using a μ-expression is: letrec x1: τ1→τ2 = λx2:τ1.e1 in e2 ===> let x1: τ1→τ2 = μx1:τ1→τ2. λx2:τ1.e1 in e2 ---------------------------------------------------------------------- Exercise: Verify that these translations work for the factorial example in (1). ---------------------------------------------------------------------- Defining a direct big-step semantics for letrec is no easier, so this would also normally be accomplished by first translating letrecs into the μ form. Note that the representation of recursive function as a value in the substitution-based big-step semantics would be a closed μ-expression. In the environment based big-step semantics, the representation of a recursive function as a value would be a closure of a μ-expression: <μf.λx.e, E>. The rule for evaluating an application would be eval(App(<μf.λx.e, E'>,v),E) = eval(e, bind(x,v,bind(f,<μf.λx.e, E'>,E') There is an SML implementation of this rule in the TFun package.