MoreStlc: 単純型付きラムダ計算についてさらに


Set Warnings "-notation-overridden,-parsing".
From PLF Require Import Maps.
From PLF Require Import Types.
From PLF Require Import Smallstep.
From PLF Require Import Stlc.
From Coq Require Import Strings.String.

STLCの単純な拡張


単純型付きラムダ計算は理論的性質を興味深いものにするには十分な構造を持っていますが、プログラミング言語といえるようなものではありません。
この章では、型における扱いが明らかないくつもの馴染み深い機能を導入することで、実世界の言語とのギャップを埋め始めます。

数値

As we saw in exercise stlc_arith at the end of the StlcProp chapter, adding types, constants, and primitive operations for natural numbers is easy -- basically just a matter of combining the Types and Stlc chapters. Adding more realistic numeric types like machine integers and floats is also straightforward, though of course the specifications of the numeric primitives become more fiddly.

let束縛


複雑な式を書くとき、部分式に名前をつけられると便利なことがよくあります。 同じことを繰り返すのを避けることができ、またしばしば可読性が向上します。 ほとんどの言語はこのための1つ以上の方法を持っています。 例えばOCaml(およびCoq)では、 let x=t1 in t2 と書くと「式t1を簡約して、その結果を名前xに束縛してt2を簡約する」ことを意味します。
ここで導入するlet束縛子はOCamlに従って値呼び(call-by-value)評価順序とします。 つまり、let本体の評価が始まる前にlet束縛項は完全に評価されます。 型付け規則T_Letは、letの型が次の手順で計算されることを示しています: まずlet束縛項の型の計算、次にその型の束縛によるコンテキストの拡張、さらにこの拡張されたコンテキストでのlet本体の型の計算をします(これがlet式全体の型になります)。
本資料のこの時点では、新しい機能の定義のためにたくさんの日本語の文章を読み通すより、同じ情報を伝える規則を単に見る方が、おそらく簡単でしょう。以下がその規則です:

構文:
       t ::=                項
           | ...               (前と同じ他の項)
           | let x=t in t      let束縛

簡約:
                                 t1 --> t1' 
                     ----------------------------------               (ST_Let1) 
                     let x=t1 in t2 --> let x=t1' in t2 
 
                        ----------------------------              (ST_LetValue) 
                        let x=v1 in t2 --> [x:=v1]t2 
型付け:
             Gamma |- t1 \in T1      x|->T1; Gamma |- t2 \in T2 
             --------------------------------------------------        (T_Let) 
                        Gamma |- let x=t1 in t2 \in T2 


ここまでのCoqを用いた関数プログラミングの例では、値の対(pairs)を頻繁に使ってきました。 そのような対の型は「直積型(product type)」と呼ばれます。
対の形式化はほとんど議論する余地がないほど簡単です。 しかし、共通パターンを強調するため、定義のいろいろな部分をちょっと見てみましょう。

Coqでは、対の構成要素を抽出する基本的な方法は、パターンマッチング(pattern matching)です。 別の方法としては、fstsnd、つまり、1番目と2番目の要素の射影操作を基本操作として持つ方法があります。 お遊びで、対をこの方法でやってみましょう。 例えば、数値の対をとり、その和と差の対を返す関数の書き方は次の通りです:
       \x : Nat*Nat. 
          let sum = x.fst + x.snd in 
          let diff = x.fst - x.snd in 
          (sum,diff) 

これから、単純型付きラムダ計算に対を追加するには、2つの新しい項の形を追加することが含まれます。 1つは対で (t1,t2) と書きます。もう1つは射影で、第1射影は t.fst、第2射影は t.sndと書きます。 さらに1つの型コンストラクタ T1*T2 を追加します。 これをT1T2の直積と呼びます。

構文:
       t ::=                項
           | ... 
           | (t,t)             対
           | t.fst             第1射影
           | t.snd             第2射影
 
       v ::=                値
           | ... 
           | (v,v)             対値
 
       T ::=                型
           | ... 
           | T * T             直積型

簡約については、対と射影がどう振る舞うかを規定するいくつかの新しい規則が必要です。
                              t1 --> t1' 
                         --------------------                        (ST_Pair1) 
                         (t1,t2) --> (t1',t2) 
 
                              t2 --> t2' 
                         --------------------                        (ST_Pair2) 
                         (v1,t2) --> (v1,t2') 
 
                              t1 --> t1' 
                          ------------------                          (ST_Fst1) 
                          t1.fst --> t1'.fst 
 
                          ------------------                       (ST_FstPair) 
                          (v1,v2).fst --> v1 
 
                              t1 --> t1' 
                          ------------------                          (ST_Snd1) 
                          t1.snd --> t1'.snd 

                           ------------------                       (ST_SndPair) 
                          (v1,v2).snd --> v2 

規則ST_FstPairST_SndPairは、完全に簡約された対が第1射影または第2射影に遭遇したとき、結果が対応する要素であると言っています。 合同規則ST_Fst1ST_Snd1は、射影の対象の項が完全に評価されきっていないとき、対象の簡約を認めるものです。 ST_Pair1ST_Pair2は対の構成部分の簡約です。 最初に左の部分を簡約し、それが値となったら、次に右の部分を簡約します。 これらの規則内におけるメタ変数vtの使い方は、対に関して左から右に評価する戦略(left-to-right evaluation strategy)であることを規定します。 (暗黙の慣習として、vv1などのメタ変数は値のみを指すものとしています。) また値の定義に節を加え、(v1,v2) が値であることを規定しています。 値の対自体が値でなければならないという事実は、関数の引数として渡された対が、関数の本体の実行が開始される前に完全に簡約されることを保証します。

対と射影の型付け規則はそのまま直ぐに得られます。
               Gamma |- t1 \in T1     Gamma |- t2 \in T2 
               -----------------------------------------               (T_Pair) 
                       Gamma |- (t1,t2) \in T1*T2 
 
                        Gamma |- t \in T1*T2 
                        ---------------------                          (T_Fst) 
                        Gamma |- t.fst \in T1 
 
                        Gamma |- t \in T1*T2 
                        ---------------------                          (T_Snd) 
                        Gamma |- t.snd \in T2 

T_Pairは、t1が型T1を持ち、t2が型T2を持つならば、 (t1,t2) が型 T1*T2 を持つことを言っています。 逆に、T_FstT_Sndは、tが直積型T1*T2を持つ(つまり評価結果が対になる)ならば、射影の型はT1T2になることを定めます。

Unit


もう一つの便利な基本型は、MLファミリーの言語に特に見られるものですが、1要素の型Unitです。 この型は要素を1つ持ちます。それは項定数unitです(先頭の文字は小文字のuです)。 型付け規則は unitUnitの要素と定めます。 取りうる値の集合にもunitを加えます。 実際、unitは型Unitの式の評価結果としてとり得る唯一の値です。

構文:
       t ::=                項
           | ...               (前と同様)
           | unit              unit値
 
       v ::=                値
           | ... 
           | unit              unit 
 
       T ::=                型
           | ... 
           | Unit              Unit型
型付け:
                         ----------------------                        (T_Unit) 
                         Gamma |- unit \in Unit 

1つの要素だけしか持たない型を定義することにわずらわされるのは、少々奇妙なことに見えるかもしれません。 結局のところ、このような型に属する計算は自明なものだけではないのでしょうか?
これは妥当な質問です。 実際STLCではUnit型は特別、問題ではありません(ちょっと後でこの型の使い道を二つ見ることになりますが)。 Unitが本当に便利なのはよりリッチな言語で副作用(side effects)を持つ場合です。 例えば、変更可能な変数やポインタについての代入文や、例外その他のローカルではないコントロール機構を持つ場合、などです。 そのような言語では、副作用のためだけに評価される式の(どうでもよい)結果のための型が便利なのです。

直和


多くのプログラムでは2つの異なった形を持つ値を扱うことが求められます。 例えば大学のデータベースから学生を調べるのに、名前か、「または」、IDナンバーを使うという場合があります。 検索関数は、マッチした値か、「または」、エラーコードを返すかもしれません。
これらは、2項の直和型(sum type または disjoint union)の例です。 直和型は2つの与えられた型の一方から抽出した値の集合にあたります。 例えば次のようなものです。
       Nat + Bool 
この型の要素を、それぞれの構成部分の型の要素にタグ付けする(tagging)ことで生成します。 例えば、nNatならば inl nNat+Bool の要素です。 同様に、bBoolならば inr bNat+Bool の要素です。 タグの名前inlinrは、これらのタグを関数と考えるところから出ています。
       inl \in Nat  -> Nat + Bool 
       inr \in Bool -> Nat + Bool 
これらの関数は、NatまたはBoolの要素を直和型Nat+Boolの左部分または右部分に注入("inject")します。 (しかし、実際にはこれらを関数としては扱いません。 inlinrはキーワードで、inl tinr t は基本構文形であり、関数適用ではありません。)

一般に、型 T1 + T2 の要素は T1の要素にinlをタグ付けしたものと、 T2の要素にinrをタグ付けしたものから成ります。

Coqでのプログラミングで見たように、直和型の重要な用途の一つに、エラー通知があります。
      div \in Nat -> Nat -> (Nat + Unit) 
      div = 
        \x:Nat. \y:Nat. 
          test iszero y then 
            inr unit 
          else 
            inl ... 
この型 Nat + Unit は Coq における option nat と同型です。 つまり、簡単に両方向へ変換する関数が書けます。

直和型の要素を「利用する」ために、分解する構文としてcase構文を導入します(これはCoqのmatchの非常に単純化された形です)。 例えば、以下の手続きは、Nat+BoolNatに変換します:
    getNat \in Nat+Bool -> Nat 
    getNat = 
      \x:Nat+Bool. 
        case x of 
          inl n => n 
        | inr b => if b then 1 else 0 
より形式的に...

構文:
       t ::=                項
           | ...               (前と同様)
           | inl T t           タグ付け(左)
           | inr T t           タグ付け(右)
           | case t of         case 
               inl x => t 
             | inr x => t 
 
       v ::=                値
           | ... 
           | inl T v           タグ付き値(左)
           | inr T v           タグ付き値(右)
 
       T ::=                型
           | ... 
           | T + T             直和型

簡約:
                               t1 --> t1' 
                        ------------------------                       (ST_Inl) 
                        inl T2 t1 --> inl T2 t1' 
 
                               t2 --> t2' 
                        ------------------------                       (ST_Inr) 
                        inr T1 t2 --> inr T1 t2' 
 
                               t0 --> t0' 
               -------------------------------------------            (ST_Case) 
                case t0 of inl x1 => t1 | inr x2 => t2 --> 
               case t0' of inl x1 => t1 | inr x2 => t2 
 
            -----------------------------------------------        (ST_CaseInl) 
            case (inl T2 v1) of inl x1 => t1 | inr x2 => t2 
                           -->  [x1:=v1]t1 
 
            -----------------------------------------------        (ST_CaseInr) 
            case (inr T1 v2) of inl x1 => t1 | inr x2 => t2 
                           -->  [x2:=v1]t2 

型付け:
                          Gamma |- t1 \in T1 
                   ------------------------------                       (T_Inl) 
                   Gamma |- inl T2 t1 \in T1 + T2 
 
                          Gamma |- t2 \in T2 
                   -------------------------------                      (T_Inr) 
                    Gamma |- inr T1 t2 \in T1 + T2 
 
                        Gamma |- t \in T1+T2 
                     x1|->T1; Gamma |- t1 \in T 
                     x2|->T2; Gamma |- t2 \in T 
         ----------------------------------------------------          (T_Case) 
         Gamma |- case t of inl x1 => t1 | inr x2 => t2 \in T 
inlinrに型を付記する理由は、関数に対して行ったのと同様、型付け規則を単純にするためです。

この情報がなければ、型推論規則T_Inlは、例えば、t1が型T1の要素であることを示した後、「任意の」型T2について inl t1T1 + T2 の要素であることを導出できます。 例えば、inl 5 : Nat + Natinl 5 : Nat + Bool の両方(および無数の型)が導出できます。 この奇妙さ(技術的に言えば型の一意性の欠如)は、型チェックアルゴリズムを、ここまでやってきたように「規則を下から上に読む」という単純な方法で構築することができなくなることを意味します。
この問題を扱うのにはいろいろな方法があります。 簡単なものの1つは、ここで採用する方法ですが、単射を実行するときに直和型の「別の側」をプログラマが明示的に付記することを強制するというものです。 これはプログラマにはかなり重い作業です(そのため実際の言語は別の解法を採用しています)。 しかし、理解と形式化は容易な方法です。

リスト


ここまで見てきた型付け機能は、Boolのような基本型(base types)と、古い型から新しい型を構築する->*のような型コンストラクタ(type constructors)に分類できます。 もう一つの有用な型コンストラクタがListです。 すべての型Tについて、型 List TTから抽出したものを要素とする有限長リストを表します。
原理的には、直積型や直和型、再帰型(recursive types)を用いることでリストを定義できます。 しかし、再帰型に意味を与えることは簡単ではありません。 その代わりに、リストの特別な場合を直接議論します。
以下にリストの構文、意味、型付け規則を与えます。 nilに対して明示的に型を付記することが必須であり、consには付記できないという点を除けば、ここで定義されたリストは本質的にCoqで構築したものと同じです。 リストを分解するためにlcaseを使います。 これは「空リストのheadは何か?」というような問題を扱うことを避けるためです。

従って、例えば、数値リストの最初の2つの要素の和を計算する関数は次の通りです:
      \x:List Nat. 
      lcase x of nil   => 0 
               | a::x' => lcase x' of nil    => a 
                                    | b::x'' => a+b 
構文:
       t ::=                項
           | ... 
           | nil T 
           | cons t t 
           | lcase t of nil  => t 
                      | x::x => t 
 
       v ::=                値
           | ... 
           | nil T             nil値
           | cons v v          cons値
 
       T ::=                型
           | ... 
           | List T            Tのリスト

簡約:
                                t1 --> t1' 
                       --------------------------                    (ST_Cons1) 
                       cons t1 t2 --> cons t1' t2 
 
                                t2 --> t2' 
                       --------------------------                    (ST_Cons2) 
                       cons v1 t2 --> cons v1 t2' 
 
                              t1 --> t1' 
                -------------------------------------------         (ST_Lcase1) 
                 (lcase t1 of nil => t2 | xh::xt => t3) --> 
                (lcase t1' of nil => t2 | xh::xt => t3) 
 
               -----------------------------------------          (ST_LcaseNil) 
               (lcase nil T of nil => t2 | xh::xt => t3) 
                                --> t2 
 
            ------------------------------------------------     (ST_LcaseCons) 
            (lcase (cons vh vt) of nil => t2 | xh::xt => t3) 
                          --> [xh:=vh,xt:=vt]t3 

型付け:
                        -------------------------                       (T_Nil) 
                        Gamma |- nil T \in List T 
 
             Gamma |- t1 \in T      Gamma |- t2 \in List T 
             ---------------------------------------------             (T_Cons) 
                    Gamma |- cons t1 t2 \in List T 
 
                        Gamma |- t1 \in List T1 
                        Gamma |- t2 \in T 
                (h|->T1; t|->List T1; Gamma) |- t3 \in T 
          ---------------------------------------------------         (T_Lcase) 
          Gamma |- (lcase t1 of nil => t2 | h::t => t3) \in T 

一般再帰


(Coqを含む)ほとんどのプログラミング言語に現れるもう1つの機構が、再帰関数を定義する機能です。 例えば、階乗関数を次のように定義できるとよいと思うでしょう:
      fact = \x:Nat. 
                test x=0 then 1 else x * (fact (pred x))) 
この束縛子の中では、上で導入したletと異なり、右辺の変数factが束縛されることになります。
直接「再帰的定義」を形式化することも可能ですが、なかなかの作業が必要になります。 特にstep内で再帰関数定義の「環境(environment)」を持ち回る必要があるでしょう。

ここでは、冗長ではありますが、同じくらい強力で、直接の形式化が容易な形での再帰関数の定義方法を取ります。 再帰的定義を書く代わりに、fixという名前の「不動点演算子(fixed-point operator)」を定義します。 不動点演算子は、簡約の過程で必要に応じて再帰的定義の右辺に「展開」("unfold")するものです。
例えば、
      fact = \x:Nat. 
                test x=0 then 1 else x * (fact (pred x))) 
のように書く代わりに、次のように書きます。
      fact = 
          fix 
            (\f:Nat->Nat. 
               \x:Nat. 
                  test x=0 then 1 else x * (f (pred x))) 
前者の書き方から、以下のようにして後者の書き方を得ます。
  • factの定義の右辺から再帰的定義の対象であるfactという名前を新しい変数名fで置き換えます。
  • fを束縛する抽象を、適切な方注釈と共に追加します。 (ffactの代わりに使っているので、factが型Nat->Natを持っていることから、fに同じ型Nat->Natを付けます。) 全体の型は(Nat->Nat) -> (Nat->Nat)になります。
  • この抽象にfixを適用します。 その結果の型はNat->Natになります。
  • この全体を、factに対する通常のlet束縛の右辺として利用します。

直観的にはfixに渡される高階関数ffact関数の生成器(generator)です。 fが、factに求められる振る舞いをnまで「近似」する関数(つまり、n以下の入力に対しては正しい結果を返すがnより大きい入力に関してはどうでもいい関数)に適用されるとき、fはより良い近似、つまり、n+1まで正しい答えを返す関数、を返します。 fixをこの生成器に適用すると、生成器の「不動点(fixed point)」を返します。 この不動点は、すべての入力nについて求められる振る舞いをする関数です。
(ここでは不動点("fixed point")という用語を通常の数学と同じ意味で使っています。 通常の数学では、関数fの不動点とは、f(x) = x となる入力 x のことです。 ここでは、型 (Nat->Nat)->(Nat->Nat) を持つ関数Fの不動点は、Nat->Nat型の関数fのうち、F ffと同じように振る舞うものです。)

構文:
       t ::=                項
           | ... 
           | fix t             不動点演算子
簡約:
                                t1 --> t1' 
                            ------------------                        (ST_Fix1) 
                            fix t1 --> fix t1' 
 
               --------------------------------------------         (ST_FixAbs) 
               fix (\xf:T1.t2) --> [xf:=fix (\xf:T1.t2)] t2 
型付け:
                           Gamma |- t1 \in T1->T1 
                           ----------------------                       (T_Fix) 
                           Gamma |- fix t1 \in T1 

fact 3 = fix F 3 の動きを追うことで、 ST_FixAbs がどのように動くのか見ます。 ここで、Fは以下の式とします。
    F = (\f. \x. test x=0 then 1 else x * (f (pred x))) 
(可読性のために型注釈は省略します。)
    fix F 3 
--> ST_FixAbs + ST_App1
    (\x. test x=0 then 1 else x * (fix F (pred x))) 3 
--> ST_AppAbs
    test 3=0 then 1 else 3 * (fix F (pred 3)) 
--> ST_Test0_Nonzero
    3 * (fix F (pred 3)) 
--> ST_FixAbs + ST_Mult2
    3 * ((\x. test x=0 then 1 else x * (fix F (pred x))) (pred 3)) 
--> ST_PredNat + ST_Mult2 + ST_App2
    3 * ((\x. test x=0 then 1 else x * (fix F (pred x))) 2) 
--> ST_AppAbs + ST_Mult2
    3 * (test 2=0 then 1 else 2 * (fix F (pred 2))) 
--> ST_Test0_Nonzero + ST_Mult2
    3 * (2 * (fix F (pred 2))) 
--> ST_FixAbs + 2 x ST_Mult2
    3 * (2 * ((\x. test x=0 then 1 else x * (fix F (pred x))) (pred 2))) 
--> ST_PredNat + 2 x ST_Mult2 + ST_App2
    3 * (2 * ((\x. test x=0 then 1 else x * (fix F (pred x))) 1)) 
--> ST_AppAbs + 2 x ST_Mult2
    3 * (2 * (test 1=0 then 1 else 1 * (fix F (pred 1)))) 
--> ST_Test0_Nonzero + 2 x ST_Mult2
    3 * (2 * (1 * (fix F (pred 1)))) 
--> ST_FixAbs + 3 x ST_Mult2
    3 * (2 * (1 * ((\x. test x=0 then 1 else x * (fix F (pred x))) (pred 1)))) 
--> ST_PredNat + 3 x ST_Mult2 + ST_App2
    3 * (2 * (1 * ((\x. test x=0 then 1 else x * (fix F (pred x))) 0))) 
--> ST_AppAbs + 3 x ST_Mult2
    3 * (2 * (1 * (test 0=0 then 1 else 0 * (fix F (pred 0))))) 
--> ST_Test0Zero + 3 x ST_Mult2
    3 * (2 * (1 * 1)) 
--> ST_MultNats + 2 x ST_Mult2
    3 * (2 * 1) 
--> ST_MultNats + ST_Mult2
    3 * 2 
--> ST_MultNats
    6 

重要な点として、CoqのFixpointと異なり、fixでの定義は発散するような関数も書けます。

練習問題: ★, standard, optional (halve_fix)

次の再帰的定義をfixを用いた定義に直しなさい:
      halve = 
        \x:Nat. 
           test x=0 then 0 
           else test (pred x)=0 then 0 
           else 1 + (halve (pred (pred x))) 

Exercise: 1 star, standard, optional (fact_steps)

Write down the sequence of steps that the term fact 1 goes through to reduce to a normal form (assuming the usual reduction rules for arithmetic operations).

練習問題: ★, optional (fact_steps)

fact 1 が正規形まで簡約されるステップ列を書き下しなさい(ただし、算術演算については通常の簡約規則を仮定します)。

任意のTについて型 T->T の関数の不動点が記述できる形ができたことから、驚くようなことが起こります。 特に、すべての型が何らかの項に住まれている(inhabited)ということが導かれます。 これを確認するため、すべての型Tについて、項
    fix (\x:T.x)
が定義できることを見てみましょう。 T_FixT_Absから、この項は型Tを持ちます。 ST_FixAbsより、この項を簡約すると何度やっても自分自身になります。 したがって、この項はTの発散要素(diverging element)です。
より有用なこととして、次はfixを使って2引数の再帰関数を定義する例です:
    equal = 
      fix 
        (\eq:Nat->Nat->Bool. 
           \m:Nat. \n:Nat. 
             test m=0 then iszero n 
             else test n=0 then fls 
             else eq (pred m) (pred n)) 
そして最後に、次はfixを使って再帰関数の対を定義する例です(この例は、規則T_Fixの型T1は関数型ではなくてもよいことを示しています):
      evenodd = 
        fix 
          (\eo: (Nat->Bool * Nat->Bool). 
             let e = \n:Nat. test n=0 then tru else eo.snd (pred n) in 
             let o = \n:Nat. test n=0 then fls else eo.fst (pred n) in 
             (e,o)) 
 
      even = evenodd.fst 
      odd  = evenodd.snd 

レコード


STLCの基本拡張の最後の例として、 レコード(records)とその型をどのように形式化するかをちょっと見てみましょう。 直観的には、レコードは組に二つの一般化をほどこすことで得られます。 (二つの代わりに)n-要素の組とすること、そして(位置の代わりに)「ラベル(label)」で要素へアクセスできることです。

構文:
       t ::=                          項
           | ... 
           | {i1=t1, ..., in=tn}         レコード
           | t.i                         射影
 
       v ::=                          値
           | ... 
           | {i1=v1, ..., in=vn}         レコード値
 
       T ::=                          型
           | ... 
           | {i1:T1, ..., in:Tn}         レコード型

直積からの一般化はかなり明確です。 しかし、ここで実際に記述したものは、以前の章で書いたものよりかなり非形式的であることは注意しておくべきです。 いろいろなところで、"..."を「これらを何個か」という意味で使っていますし、レコードに同じラベルが繰り返し出てきてはいけない、という通常の付加条件を明示的に述べることを省いています。

簡約:
                              ti --> ti' 
                 ------------------------------------                  (ST_Rcd) 
                     {i1=v1, ..., im=vm, in=ti , ...} 
                 --> {i1=v1, ..., im=vm, in=ti', ...} 
 
                              t1 --> t1' 
                            --------------                           (ST_Proj1) 
                            t1.i --> t1'.i 
 
                      -------------------------                    (ST_ProjRcd) 
                      {..., i=vi, ...}.i --> vi 
これらの規則も、やはりちょっと非形式的です。 例えば、最初の規則は「tiが値でないフィールドのうち最も左のもので、titi'にステップで進むならば、レコード全体のステップは...」と読まれることを意図しています。 最後の規則では、i と呼ばれるフィールドは1つだけで、他のすべてのフィールドは値を持っていることを意図しています。

型付けも簡単です:
            Gamma |- t1 \in T1     ...     Gamma |- tn \in Tn 
          ----------------------------------------------------          (T_Rcd) 
          Gamma |- {i1=t1, ..., in=tn} \in {i1:T1, ..., in:Tn} 
 
                    Gamma |- t \in {..., i:Ti, ...} 
                    -------------------------------                    (T_Proj) 
                          Gamma |- t.i \in Ti 

上述の定義を精密にする方法はいくつもあります。
  • 構文の形と推論規則を、上記の形になるべく近いまま直接形式化するという方法があります。 これは概念的に「直球」で、もし本当のコンパイラを作るならば、おそらくいちばん合っているでしょう。 特に、形式化の中にエラーメッセージのプリントを含めることができるので、プログラマが理解するのが容易になるでしょう。 しかし、規則の形式化版はまったくきれいにはなりませんし、扱いも難しいものになります。 というのも、...で省略した部分を全て明示的に定量化し、理解しなければならないからです。 このため、レコードによる拡張は本章の追加課題にも入れていません。 (それでもここで非形式的に議論しておくのは、Sub章における部分型付けのモチベーションになるからです。)
  • 別の方法として、レコードを表現する、よりスムーズな方法を探すことができます。 例えば、1つのコンストラクタでレコード全体を一度に作るのではなく、空レコードに対応する1つのコンストラクタと、存在するレコードに1つのフィールドを追加する別のコンストラクタの2つで表現するという方法があります。 この方法は、レコードについての計算のメタ理論に一番の興味がある場合には正しい方法です。 なぜなら、この方法をとると、きれいで優雅な定義と証明が得られるからです。 Records章では、この方法がどのように行えるかを示しています。
  • 最後に、望むならば、レコードを形式化することを完全に避けることができます。 このためには、レコード記法は、対と直積型を含むより複雑な式の単なる非形式的な略記法である、と規定します。 次節では、このアプローチの概略を与えます。

レコードのエンコード (Optional)


では、レコードをunitと対で表現する方法を見ていきましょう。 (この素晴らしい表現は、Luca Cardelliによる提案です。 この表現により部分型付けを追加することもできます。)
最初に、任意のサイズの「組(tuple)」が対とunit値のネストでエンコードできることを確認します。 対の記法 (t1,t2) を混用するのを避けるため、組を書き下すためにはラベルを持たない中カッコ({..})を使います。 つまり、{}は0個組、{5}は一つ組、{5,6}は二つ組(事実上対と同じ)、{5,6,7}は三つ組、等です。
    {}                 ---->  unit 
    {t1, t2, ..., tn}  ---->  (t1, trest) 
                              ただし {t2, ..., tn} ----> trest
同様に、組の型を直積型のネストでエンコードすることができます。
    {}                 ---->  Unit 
    {T1, T2, ..., Tn}  ---->  T1 * TRest 
                              ただし {T2, ..., Tn} ----> TRest
組のフィールドの射影演算は、第2射影の列に続く第1射影としてエンコードできます:
    t.0        ---->  t.fst 
    t.(n+1)    ---->  (t.snd).n 
次に、レコードラベルに何らかの全順序があり、そのため各ラベルに一意に自然数が関連づけられると仮定します。 この数をラベルの「ポジション(position)」と呼びます。 例えば、以下のようにポジションが定められるとします:
      LABEL   POSITION 
      a       0 
      b       1 
      c       2 
      ...     ... 
      bar     1395 
      ...     ... 
      foo     4460 
      ...     ... 
このポジションを、レコード値を組(つまりネストされた対)でエンコードするために使います。 つまり、フィールドをそのポジションでソートします。 例えば:
      {a=5, b=6}      ---->   {5,6} 
      {a=5, c=7}      ---->   {5,unit,7} 
      {c=7, a=5}      ---->   {5,unit,7} 
      {c=5, b=3}      ---->   {unit,3,5} 
      {f=8,c=5,a=7}   ---->   {7,unit,5,unit,unit,8} 
      {f=8,c=5}       ---->   {unit,unit,5,unit,unit,8} 
以下のことに注意します。 各フィールドはそのラベルに関連づけられたポジションに現れます。 また、組の大きさは、最大のポジションを持つラベルによって決定されます。 そして、使われていないポジションはunitで埋められます。
レコードの型についてもまったくおなじことをします:
      {a:Nat, b:Nat}      ---->   {Nat,Nat} 
      {c:Nat, a:Nat}      ---->   {Nat,Unit,Nat} 
      {f:Nat, c:Nat       ---->   {Unit,Unit,Nat,Unit,Unit,Nat} 
最後に、レコードの射影は適切なポジションについての組の射影でエンコードされます:
      t.l  ---->  t.(position of l) 
レコードのオリジナルの「直接の」表現に対するすべての型付け規則が、このエンコードで正しいことをチェックするのは難しいことではありません。 (簡約規則は正しさを「ほぼ」確認できます。完全に、ではありません。 なぜなら、フィールドの並べ直しによってフィールドの簡約順序が変わりうるためです。)

もちろん、ラベルfooを持つレコードをたまたま使ったときには、このエンコードはあまり効率的ではありません。 しかしこの問題は見た目ほど深刻ではありません。 もし、コンパイラがプログラム全体を同時に見ることができると仮定するなら、ラベルの番号づけをうまく選んで、一番よく使われるラベルに小さいポジションを与えることができます。 実際、商用のコンパイラで本質的にこれをやっているものもあります!

バリアント (Optional)


直積がレコードに一般化できたのと同様、直和は n-個のラベルを持った型「バリアント」(variants)に一般化できます。 T1+T2 の代わりに <l1:T1,l2:T2,...ln:Tn> のように書くことができます。 ここでl1l2、... はフィールドラベルで、インスタンスの構成と、case のラベルの両方に使われます。
n-個のバリアントは、リストや木構造のような任意の帰納的データ型をゼロから構築するのに必要なメカニズムのほとんどを与えます。 唯一足りないのは、型定義の再帰です。ここではこの話題は扱いません。 ただ、詳細な扱いはいろいろなテキストブックに書かれています。 例えば "Types and Programming Languages" Pierce 2002 (Bib.v内)です。

練習問題: 拡張を形式化する


Module STLCExtended.

練習問題: ★★★, standard (STLCE_definitions)

ここからの一連の課題では、本章で説明した拡張の形式化をしてもらいます。 項と型の構文に必要な拡張は提示しておきました。 また、自分の定義が期待された通りに動作するかをテストできるように、いくつかの例を示しておきました。 定義の残りの部分を埋め、それに合わせて証明を拡張しなさい。
(訳注:この章のこれ以降はすべて一連の練習問題です。 埋めるのはその中の「FILL IN HERE」という部分です。)
取りかかるために、以下のものに関しては実装しておきました:
  • 数値
  • 直和
  • リスト
  • Unit型
読者は、以下のものに関する実装を完成させなさい:
  • 直積
  • let (束縛を含む)
  • fix
よい戦略は、ファイルの最初から最後までを1パスで行おうとせずに、2パスで拡張項目を一つづつやることです。 定義または証明のそれぞれについて、提示されたパーツを注意深く読むことから始めなさい。 その際に、ハイレベルの直観についてはStlc章のテキストを参照し、詳細の機構については、埋め込まれたコメントを参照しなさい。

構文


Inductive ty : Type :=
  | Arrow : ty -> ty -> ty
  | Nat : ty
  | Sum : ty -> ty -> ty
  | List : ty -> ty
  | Unit : ty
  | Prod : ty -> ty -> ty.

Inductive tm : Type :=
  
  | var : string -> tm
  | app : tm -> tm -> tm
  | abs : string -> ty -> tm -> tm
  
  | const : nat -> tm
  | scc : tm -> tm
  | prd : tm -> tm
  | mlt : tm -> tm -> tm
  | test0 : tm -> tm -> tm -> tm
  
  | tinl : ty -> tm -> tm
  | tinr : ty -> tm -> tm
  | tcase : tm -> string -> tm -> string -> tm -> tm
          
  
  | tnil : ty -> tm
  | tcons : tm -> tm -> tm
  | tlcase : tm -> tm -> string -> string -> tm -> tm
           
  
  | unit : tm

  

  
  | pair : tm -> tm -> tm
  | fst : tm -> tm
  | snd : tm -> tm
  
  | tlet : string -> tm -> tm -> tm
         
  
  | tfix : tm -> tm.

なお、簡潔にするため、ブール値をなくし、その代わりゼロテストと条件分岐を組み合わせた test0 構文を提供しています。 つまり、
       test x = 0 then ... else ... 
と書く代わりに、次のように書きます:
       test0 x then ... else ... 

置換


Fixpoint subst (x : string) (s : tm) (t : tm) : tm :=
  match t with
  
  | var y =>
      if eqb_string x y then s else t
  | abs y T t1 =>
      abs y T (if eqb_string x y then t1 else (subst x s t1))
  | app t1 t2 =>
      app (subst x s t1) (subst x s t2)
  
  | const n =>
      const n
  | scc t1 =>
      scc (subst x s t1)
  | prd t1 =>
      prd (subst x s t1)
  | mlt t1 t2 =>
      mlt (subst x s t1) (subst x s t2)
  | test0 t1 t2 t3 =>
      test0 (subst x s t1) (subst x s t2) (subst x s t3)
  
  | tinl T t1 =>
      tinl T (subst x s t1)
  | tinr T t1 =>
      tinr T (subst x s t1)
  | tcase t0 y1 t1 y2 t2 =>
      tcase (subst x s t0)
         y1 (if eqb_string x y1 then t1 else (subst x s t1))
         y2 (if eqb_string x y2 then t2 else (subst x s t2))
  
  | tnil T =>
      tnil T
  | tcons t1 t2 =>
      tcons (subst x s t1) (subst x s t2)
  | tlcase t1 t2 y1 y2 t3 =>
      tlcase (subst x s t1) (subst x s t2) y1 y2
        (if eqb_string x y1 then
           t3
         else if eqb_string x y2 then t3
              else (subst x s t3))
  
  | unit => unit

  

  
  
  
  
  
  
  | _ => t
  end.

Notation "'[' x ':=' s ']' t" := (subst x s t) (at level 20).

簡約


次にこの言語の値を定義します。

Inductive value : tm -> Prop :=
  
  | v_abs : forall x T11 t12,
      value (abs x T11 t12)
  
  | v_nat : forall n1,
      value (const n1)
  
  | v_inl : forall v T,
      value v ->
      value (tinl T v)
  | v_inr : forall v T,
      value v ->
      value (tinr T v)
  
  | v_lnil : forall T, value (tnil T)
  | v_lcons : forall v1 vl,
      value v1 ->
      value vl ->
      value (tcons v1 vl)
  
  | v_unit : value unit
  
  | v_pair : forall v1 v2,
      value v1 ->
      value v2 ->
      value (pair v1 v2).

Hint Constructors value.

Reserved Notation "t1 '-->' t2" (at level 40).

Inductive step : tm -> tm -> Prop :=
  
  | ST_AppAbs : forall x T11 t12 v2,
         value v2 ->
         (app (abs x T11 t12) v2) --> [x:=v2]t12
  | ST_App1 : forall t1 t1' t2,
         t1 --> t1' ->
         (app t1 t2) --> (app t1' t2)
  | ST_App2 : forall v1 t2 t2',
         value v1 ->
         t2 --> t2' ->
         (app v1 t2) --> (app v1 t2')
  
  | ST_Succ1 : forall t1 t1',
       t1 --> t1' ->
       (scc t1) --> (scc t1')
  | ST_SuccNat : forall n1,
       (scc (const n1)) --> (const (S n1))
  | ST_Pred : forall t1 t1',
       t1 --> t1' ->
       (prd t1) --> (prd t1')
  | ST_PredNat : forall n1,
       (prd (const n1)) --> (const (pred n1))
  | ST_Mult1 : forall t1 t1' t2,
       t1 --> t1' ->
       (mlt t1 t2) --> (mlt t1' t2)
  | ST_Mult2 : forall v1 t2 t2',
       value v1 ->
       t2 --> t2' ->
       (mlt v1 t2) --> (mlt v1 t2')
  | ST_Mulconsts : forall n1 n2,
       (mlt (const n1) (const n2)) --> (const (mult n1 n2))
  | ST_Test01 : forall t1 t1' t2 t3,
       t1 --> t1' ->
       (test0 t1 t2 t3) --> (test0 t1' t2 t3)
  | ST_Test0Zero : forall t2 t3,
       (test0 (const 0) t2 t3) --> t2
  | ST_Test0Nonzero : forall n t2 t3,
       (test0 (const (S n)) t2 t3) --> t3
  
  | ST_Inl : forall t1 t1' T,
        t1 --> t1' ->
        (tinl T t1) --> (tinl T t1')
  | ST_Inr : forall t1 t1' T,
        t1 --> t1' ->
        (tinr T t1) --> (tinr T t1')
  | ST_Case : forall t0 t0' x1 t1 x2 t2,
        t0 --> t0' ->
        (tcase t0 x1 t1 x2 t2) --> (tcase t0' x1 t1 x2 t2)
  | ST_CaseInl : forall v0 x1 t1 x2 t2 T,
        value v0 ->
        (tcase (tinl T v0) x1 t1 x2 t2) --> [x1:=v0]t1
  | ST_CaseInr : forall v0 x1 t1 x2 t2 T,
        value v0 ->
        (tcase (tinr T v0) x1 t1 x2 t2) --> [x2:=v0]t2
  
  | ST_Cons1 : forall t1 t1' t2,
       t1 --> t1' ->
       (tcons t1 t2) --> (tcons t1' t2)
  | ST_Cons2 : forall v1 t2 t2',
       value v1 ->
       t2 --> t2' ->
       (tcons v1 t2) --> (tcons v1 t2')
  | ST_Lcase1 : forall t1 t1' t2 x1 x2 t3,
       t1 --> t1' ->
       (tlcase t1 t2 x1 x2 t3) --> (tlcase t1' t2 x1 x2 t3)
  | ST_LcaseNil : forall T t2 x1 x2 t3,
       (tlcase (tnil T) t2 x1 x2 t3) --> t2
  | ST_LcaseCons : forall v1 vl t2 x1 x2 t3,
       value v1 ->
       value vl ->
       (tlcase (tcons v1 vl) t2 x1 x2 t3)
         --> (subst x2 vl (subst x1 v1 t3))

  

  
  
  
  
  
  

where "t1 '-->' t2" := (step t1 t2).

Notation multistep := (multi step).
Notation "t1 '-->*' t2" := (multistep t1 t2) (at level 40).

Hint Constructors step.

型付け


Definition context := partial_map ty.

次に型付け規則を定義します。 上述の推論規則のほとんど直接の転写です。

Reserved Notation "Gamma '|-' t '\in' T" (at level 40).

Inductive has_type : context -> tm -> ty -> Prop :=
  
  | T_Var : forall Gamma x T,
      Gamma x = Some T ->
      Gamma |- (var x) \in T
  | T_Abs : forall Gamma x T11 T12 t12,
      (update Gamma x T11) |- t12 \in T12 ->
      Gamma |- (abs x T11 t12) \in (Arrow T11 T12)
  | T_App : forall T1 T2 Gamma t1 t2,
      Gamma |- t1 \in (Arrow T1 T2) ->
      Gamma |- t2 \in T1 ->
      Gamma |- (app t1 t2) \in T2
  
  | T_Nat : forall Gamma n1,
      Gamma |- (const n1) \in Nat
  | T_Succ : forall Gamma t1,
      Gamma |- t1 \in Nat ->
      Gamma |- (scc t1) \in Nat
  | T_Pred : forall Gamma t1,
      Gamma |- t1 \in Nat ->
      Gamma |- (prd t1) \in Nat
  | T_Mult : forall Gamma t1 t2,
      Gamma |- t1 \in Nat ->
      Gamma |- t2 \in Nat ->
      Gamma |- (mlt t1 t2) \in Nat
  | T_Test0 : forall Gamma t1 t2 t3 T1,
      Gamma |- t1 \in Nat ->
      Gamma |- t2 \in T1 ->
      Gamma |- t3 \in T1 ->
      Gamma |- (test0 t1 t2 t3) \in T1
  
  | T_Inl : forall Gamma t1 T1 T2,
      Gamma |- t1 \in T1 ->
      Gamma |- (tinl T2 t1) \in (Sum T1 T2)
  | T_Inr : forall Gamma t2 T1 T2,
      Gamma |- t2 \in T2 ->
      Gamma |- (tinr T1 t2) \in (Sum T1 T2)
  | T_Case : forall Gamma t0 x1 T1 t1 x2 T2 t2 T,
      Gamma |- t0 \in (Sum T1 T2) ->
      (update Gamma x1 T1) |- t1 \in T ->
      (update Gamma x2 T2) |- t2 \in T ->
      Gamma |- (tcase t0 x1 t1 x2 t2) \in T
  
  | T_Nil : forall Gamma T,
      Gamma |- (tnil T) \in (List T)
  | T_Cons : forall Gamma t1 t2 T1,
      Gamma |- t1 \in T1 ->
      Gamma |- t2 \in (List T1) ->
      Gamma |- (tcons t1 t2) \in (List T1)
  | T_Lcase : forall Gamma t1 T1 t2 x1 x2 t3 T2,
      Gamma |- t1 \in (List T1) ->
      Gamma |- t2 \in T2 ->
      (update (update Gamma x2 (List T1)) x1 T1) |- t3 \in T2 ->
      Gamma |- (tlcase t1 t2 x1 x2 t3) \in T2
  
  | T_Unit : forall Gamma,
      Gamma |- unit \in Unit

  

  
  
  
  
  
  

where "Gamma '|-' t '\in' T" := (has_type Gamma t T).

Hint Constructors has_type.

Definition manual_grade_for_extensions_definition : option (nat*string) := None.


練習問題: ★★★, standard (STLCE_examples)

この節では上述の例(および追加のいくつか)の形式化版を示します。
それぞれの例で、コメントアウトされた証明を有効にし、AdmittedQedにしなさい。 このテストをパスするように実装すること。
最初の方のものは、特定の拡張項目に焦点を当てています。 ファイルの後の方の、その拡張項目について証明を拡張するところに進む前に、これらの例を使って拡張項目についての自分の定義が適切かを確認することができます。 後の方の例はいろいろな拡張項目をまとめて必要とします。 すべての定義を埋めてからこれらの例に進む必要があるでしょう。

Module Examples.

準備


最初に、いくつか変数名を定義しましょう:

Open Scope string_scope.
Notation x := "x".
Notation y := "y".
Notation a := "a".
Notation f := "f".
Notation g := "g".
Notation l := "l".
Notation k := "k".
Notation i1 := "i1".
Notation i2 := "i2".
Notation processSum := "processSum".
Notation n := "n".
Notation eq := "eq".
Notation m := "m".
Notation evenodd := "evenodd".
Notation even := "even".
Notation odd := "odd".
Notation eo := "eo".

次に、Coq にちょっと手を入れて、型の導出の検索を自動化します。 詳細を理解する必要はまったくありません。 ざっと眺めておけば、もしautoに独自の拡張をしなければならなくなったとき、何を調べれば良いかがわかるでしょう。
以下のHint宣言は、次のように言っています: auto(Gamma |- (app e1 e1) \in T) という形のゴールに到達したときは常に、 eapply T_App を考えなさい。 この結果、中間的な型 T1 の存在変数が残ります。 lcaseについても同様です。 その存在変数は、e1e2の型導出の探索の仮定で具体化されます。 またヒントに、等号による比較の形のゴールを解く場合に「より困難なことを試しなさい」ということも追加します。 これは、T_Var(これは事前条件に等号による比較を含みます)を自動的に使用するのに便利です。

Hint Extern 2 (has_type _ (app _ _) _) =>
  eapply T_App; auto.
Hint Extern 2 (has_type _ (tlcase _ _ _ _ _) _) =>
  eapply T_Lcase; auto.
Hint Extern 2 (_ = _) => compute; reflexivity.

数値


Module Numtest.

Definition test :=
  test0
    (prd
      (scc
        (prd
          (mlt
            (const 2)
            (const 0)))))
    (const 5)
    (const 6).

Example typechecks :
  empty |- test \in Nat.
Proof.
  unfold test.
  auto 10.
Admitted.

Example numtest_reduces :
  test -->* const 5.
Proof.
Admitted.

End Numtest.

直積


Module Prodtest.

Definition test :=
  snd
    (fst
      (pair
        (pair
          (const 5)
          (const 6))
        (const 7))).

Example typechecks :
  empty |- test \in Nat.
Proof. unfold test. eauto 15. Admitted.

Example reduces :
  test -->* const 6.
Proof.
Admitted.

End Prodtest.

let


Module LetTest.

Definition test :=
  tlet
    x
    (prd (const 6))
    (scc (var x)).

Example typechecks :
  empty |- test \in Nat.
Proof. unfold test. eauto 15. Admitted.

Example reduces :
  test -->* const 6.
Proof.
Admitted.

End LetTest.

直和


Module Sumtest1.


Definition test :=
  tcase (tinl Nat (const 5))
    x (var x)
    y (var y).

Example typechecks :
  empty |- test \in Nat.
Proof. unfold test. eauto 15. Admitted.

Example reduces :
  test -->* (const 5).
Proof.
Admitted.

End Sumtest1.

Module Sumtest2.


Definition test :=
  tlet
    processSum
    (abs x (Sum Nat Nat)
      (tcase (var x)
         n (var n)
         n (test0 (var n) (const 1) (const 0))))
    (pair
      (app (var processSum) (tinl Nat (const 5)))
      (app (var processSum) (tinr Nat (const 5)))).

Example typechecks :
  empty |- test \in (Prod Nat Nat).
Proof. unfold test. eauto 15. Admitted.

Example reduces :
  test -->* (pair (const 5) (const 0)).
Proof.
Admitted.

End Sumtest2.

リスト


Module ListTest.


Definition test :=
  tlet l
    (tcons (const 5) (tcons (const 6) (tnil Nat)))
    (tlcase (var l)
       (const 0)
       x y (mlt (var x) (var x))).

Example typechecks :
  empty |- test \in Nat.
Proof. unfold test. eauto 20. Admitted.

Example reduces :
  test -->* (const 25).
Proof.
Admitted.

End ListTest.

fix


Module FixTest1.

Definition fact :=
  tfix
    (abs f (Arrow Nat Nat)
      (abs a Nat
        (test0
           (var a)
           (const 1)
           (mlt
              (var a)
              (app (var f) (prd (var a))))))).

(警告: いくつかの規則が間違っていてもfactの型チェックが通るかもしれません。)

Example typechecks :
  empty |- fact \in (Arrow Nat Nat).
Proof. unfold fact. auto 10. Admitted.

Example reduces :
  (app fact (const 4)) -->* (const 24).
Proof.
Admitted.

End FixTest1.

Module FixTest2.

Definition map :=
  abs g (Arrow Nat Nat)
    (tfix
      (abs f (Arrow (List Nat) (List Nat))
        (abs l (List Nat)
          (tlcase (var l)
            (tnil Nat)
            a l (tcons (app (var g) (var a))
                         (app (var f) (var l))))))).

Example typechecks :
  empty |- map \in
    (Arrow (Arrow Nat Nat)
      (Arrow (List Nat)
        (List Nat))).
Proof. unfold map. auto 10. Admitted.

Example reduces :
  app (app map (abs a Nat (scc (var a))))
         (tcons (const 1) (tcons (const 2) (tnil Nat)))
  -->* (tcons (const 2) (tcons (const 3) (tnil Nat))).
Proof.
Admitted.

End FixTest2.

Module FixTest3.


Definition equal :=
  tfix
    (abs eq (Arrow Nat (Arrow Nat Nat))
      (abs m Nat
        (abs n Nat
          (test0 (var m)
            (test0 (var n) (const 1) (const 0))
            (test0 (var n)
              (const 0)
              (app (app (var eq)
                              (prd (var m)))
                      (prd (var n)))))))).

Example typechecks :
  empty |- equal \in (Arrow Nat (Arrow Nat Nat)).
Proof. unfold equal. auto 10. Admitted.

Example reduces :
  (app (app equal (const 4)) (const 4)) -->* (const 1).
Proof.
Admitted.

Example reduces2 :
  (app (app equal (const 4)) (const 5)) -->* (const 0).
Proof.
Admitted.

End FixTest3.

Module FixTest4.


Definition eotest :=
  tlet evenodd
    (tfix
      (abs eo (Prod (Arrow Nat Nat) (Arrow Nat Nat))
        (pair
          (abs n Nat
            (test0 (var n)
              (const 1)
              (app (snd (var eo)) (prd (var n)))))
          (abs n Nat
            (test0 (var n)
              (const 0)
              (app (fst (var eo)) (prd (var n))))))))
  (tlet even (fst (var evenodd))
  (tlet odd (snd (var evenodd))
  (pair
    (app (var even) (const 3))
    (app (var even) (const 4))))).

Example typechecks :
  empty |- eotest \in (Prod Nat Nat).
Proof. unfold eotest. eauto 30. Admitted.

Example reduces :
  eotest -->* (pair (const 0) (const 1)).
Proof.
Admitted.

End FixTest4.

End Examples.

型付けの性質


拡張したシステムの進行と保存の証明は、純粋な単純型付きラムダ計算のものと本質的に同じです(もちろん長くはなりますが)。

進行


練習問題: ★★★, standard (STLCE_progress)

progressの証明を完成させなさい。
定理: empty |- t \in T と仮定する。すると次のいずれかである: 1. t は値、または 2. ある t' について t --> t'
証明: 型導出に関する帰納法。

Theorem progress : forall t T,
     empty |- t \in T ->
     value t \/ exists t', t --> t'.
Proof with eauto.
  intros t T Ht.
  remember empty as Gamma.
  generalize dependent HeqGamma.
  induction Ht; intros HeqGamma; subst.
  -
    
    inversion H.
  -
    
    left...
  -
    
    right.
    destruct IHHt1; subst...
    +
      destruct IHHt2; subst...
      *
        
        inversion H; subst; try solve_by_invert.
        exists (subst x t2 t12)...
      *
        
        inversion H0 as [t2' Hstp]. exists (app t1 t2')...
    +
      
      inversion H as [t1' Hstp]. exists (app t1' t2)...
  -
    left...
  -
    right.
    destruct IHHt...
    +
      inversion H; subst; try solve_by_invert.
      exists (const (S n1))...
    +
      inversion H as [t1' Hstp].
      exists (scc t1')...
  -
    right.
    destruct IHHt...
    +
      inversion H; subst; try solve_by_invert.
      exists (const (pred n1))...
    +
      inversion H as [t1' Hstp].
      exists (prd t1')...
  -
    right.
    destruct IHHt1...
    +
      destruct IHHt2...
      *
        inversion H; subst; try solve_by_invert.
        inversion H0; subst; try solve_by_invert.
        exists (const (mult n1 n0))...
      *
        inversion H0 as [t2' Hstp].
        exists (mlt t1 t2')...
    +
      inversion H as [t1' Hstp].
      exists (mlt t1' t2)...
  -
    right.
    destruct IHHt1...
    +
      inversion H; subst; try solve_by_invert.
      destruct n1 as [|n1'].
      *
        exists t2...
      *
        exists t3...
    +
      inversion H as [t1' H0].
      exists (test0 t1' t2 t3)...
  -
    destruct IHHt...
    +
      right. inversion H as [t1' Hstp]...
  -
    destruct IHHt...
    +
      right. inversion H as [t1' Hstp]...
  -
    right.
    destruct IHHt1...
    +
      inversion H; subst; try solve_by_invert.
      *
        exists ([x1:=v]t1)...
      *
        exists ([x2:=v]t2)...
    +
      inversion H as [t0' Hstp].
      exists (tcase t0' x1 t1 x2 t2)...
  -
    left...
  -
    destruct IHHt1...
    +
      destruct IHHt2...
      *
        right. inversion H0 as [t2' Hstp].
        exists (tcons t1 t2')...
    +
      right. inversion H as [t1' Hstp].
      exists (tcons t1' t2)...
  -
    right.
    destruct IHHt1...
    +
      inversion H; subst; try solve_by_invert.
      *
        exists t2...
      *
        exists ([x2:=vl]([x1:=v1]t3))...
    +
      inversion H as [t1' Hstp].
      exists (tlcase t1' t2 x1 x2 t3)...
  -
    left...


Admitted.

Definition manual_grade_for_progress : option (nat*string) := None.

コンテキスト不変性


練習問題: ★★★, standard (STLCE_context_invariance)

appears_free_in の定義を完成させ、 context_invariancefree_in_context の証明を完成させなさい。

Inductive appears_free_in : string -> tm -> Prop :=
  | afi_var : forall x,
      appears_free_in x (var x)
  | afi_app1 : forall x t1 t2,
      appears_free_in x t1 -> appears_free_in x (app t1 t2)
  | afi_app2 : forall x t1 t2,
      appears_free_in x t2 -> appears_free_in x (app t1 t2)
  | afi_abs : forall x y T11 t12,
        y <> x ->
        appears_free_in x t12 ->
        appears_free_in x (abs y T11 t12)
  
  | afi_succ : forall x t,
     appears_free_in x t ->
     appears_free_in x (scc t)
  | afi_pred : forall x t,
     appears_free_in x t ->
     appears_free_in x (prd t)
  | afi_mult1 : forall x t1 t2,
     appears_free_in x t1 ->
     appears_free_in x (mlt t1 t2)
  | afi_mult2 : forall x t1 t2,
     appears_free_in x t2 ->
     appears_free_in x (mlt t1 t2)
  | afi_test01 : forall x t1 t2 t3,
     appears_free_in x t1 ->
     appears_free_in x (test0 t1 t2 t3)
  | afi_test02 : forall x t1 t2 t3,
     appears_free_in x t2 ->
     appears_free_in x (test0 t1 t2 t3)
  | afi_test03 : forall x t1 t2 t3,
     appears_free_in x t3 ->
     appears_free_in x (test0 t1 t2 t3)
  
  | afi_inl : forall x t T,
      appears_free_in x t ->
      appears_free_in x (tinl T t)
  | afi_inr : forall x t T,
      appears_free_in x t ->
      appears_free_in x (tinr T t)
  | afi_case0 : forall x t0 x1 t1 x2 t2,
      appears_free_in x t0 ->
      appears_free_in x (tcase t0 x1 t1 x2 t2)
  | afi_case1 : forall x t0 x1 t1 x2 t2,
      x1 <> x ->
      appears_free_in x t1 ->
      appears_free_in x (tcase t0 x1 t1 x2 t2)
  | afi_case2 : forall x t0 x1 t1 x2 t2,
      x2 <> x ->
      appears_free_in x t2 ->
      appears_free_in x (tcase t0 x1 t1 x2 t2)
  
  | afi_cons1 : forall x t1 t2,
     appears_free_in x t1 ->
     appears_free_in x (tcons t1 t2)
  | afi_cons2 : forall x t1 t2,
     appears_free_in x t2 ->
     appears_free_in x (tcons t1 t2)
  | afi_lcase1 : forall x t1 t2 y1 y2 t3,
     appears_free_in x t1 ->
     appears_free_in x (tlcase t1 t2 y1 y2 t3)
  | afi_lcase2 : forall x t1 t2 y1 y2 t3,
     appears_free_in x t2 ->
     appears_free_in x (tlcase t1 t2 y1 y2 t3)
  | afi_lcase3 : forall x t1 t2 y1 y2 t3,
     y1 <> x ->
     y2 <> x ->
     appears_free_in x t3 ->
     appears_free_in x (tlcase t1 t2 y1 y2 t3)

  

  
  
  
  
  
  
.

Hint Constructors appears_free_in.

Lemma context_invariance : forall Gamma Gamma' t S,
     Gamma |- t \in S ->
     (forall x, appears_free_in x t -> Gamma x = Gamma' x) ->
     Gamma' |- t \in S.
Proof with eauto 30.
  intros. generalize dependent Gamma'.
  induction H;
    intros Gamma' Heqv...
  -
    apply T_Var... rewrite <- Heqv...
  -
    apply T_Abs... apply IHhas_type. intros y Hafi.
    unfold update, t_update.
    destruct (eqb_stringP x y)...
  -
    eapply T_Case...
    + apply IHhas_type2. intros y Hafi.
      unfold update, t_update.
      destruct (eqb_stringP x1 y)...
    + apply IHhas_type3. intros y Hafi.
      unfold update, t_update.
      destruct (eqb_stringP x2 y)...
  -
    eapply T_Lcase... apply IHhas_type3. intros y Hafi.
    unfold update, t_update.
    destruct (eqb_stringP x1 y)...
    destruct (eqb_stringP x2 y)...


Admitted.

Lemma free_in_context : forall x t T Gamma,
   appears_free_in x t ->
   Gamma |- t \in T ->
   exists T', Gamma x = Some T'.
Proof with eauto.
  intros x t T Gamma Hafi Htyp.
  induction Htyp; inversion Hafi; subst...
  -
    destruct IHHtyp as [T' Hctx]... exists T'.
    unfold update, t_update in Hctx.
    rewrite false_eqb_string in Hctx...
  -
    destruct IHHtyp2 as [T' Hctx]... exists T'.
    unfold update, t_update in Hctx.
    rewrite false_eqb_string in Hctx...
  -
    destruct IHHtyp3 as [T' Hctx]... exists T'.
    unfold update, t_update in Hctx.
    rewrite false_eqb_string in Hctx...
  -
    clear Htyp1 IHHtyp1 Htyp2 IHHtyp2.
    destruct IHHtyp3 as [T' Hctx]... exists T'.
    unfold update, t_update in Hctx.
    rewrite false_eqb_string in Hctx...
    rewrite false_eqb_string in Hctx...


Admitted.

Definition manual_grade_for_context_invariance : option (nat*string) := None.

置換


練習問題: ★★, standard (STLCE_subst_preserves_typing)

substitution_preserves_typing の証明を完成させなさい。

Lemma substitution_preserves_typing : forall Gamma x U v t S,
     (update Gamma x U) |- t \in S ->
     empty |- v \in U ->
     Gamma |- ([x:=v]t) \in S.
Proof with eauto.
  intros Gamma x U v t S Htypt Htypv.
  generalize dependent Gamma. generalize dependent S.
  induction t;
    intros S Gamma Htypt; simpl; inversion Htypt; subst...
  -
    simpl. rename s into y.
    unfold update, t_update in H1.
    destruct (eqb_stringP x y).
    +
      
      subst.
      inversion H1; subst. clear H1.
      eapply context_invariance...
      intros x Hcontra.
      destruct (free_in_context _ _ S empty Hcontra)
        as [T' HT']...
      inversion HT'.
    +
    
      apply T_Var...
  -
    rename s into y. rename t into T11.
    apply T_Abs...
    destruct (eqb_stringP x y) as [Hxy|Hxy].
    +
    
      eapply context_invariance...
      subst.
      intros x Hafi. unfold update, t_update.
      destruct (eqb_string y x)...
    +
      
      apply IHt. eapply context_invariance...
      intros z Hafi. unfold update, t_update.
      destruct (eqb_stringP y z) as [Hyz|Hyz]...
      subst.
      rewrite false_eqb_string...
  -
    rename s into x1. rename s0 into x2.
    eapply T_Case...
    +
      destruct (eqb_stringP x x1) as [Hxx1|Hxx1].
      *
        eapply context_invariance...
        subst.
        intros z Hafi. unfold update, t_update.
        destruct (eqb_string x1 z)...
      *
        apply IHt2. eapply context_invariance...
        intros z Hafi. unfold update, t_update.
        destruct (eqb_stringP x1 z) as [Hx1z|Hx1z]...
        subst. rewrite false_eqb_string...
    +
      destruct (eqb_stringP x x2) as [Hxx2|Hxx2].
      *
        eapply context_invariance...
        subst.
        intros z Hafi. unfold update, t_update.
        destruct (eqb_string x2 z)...
      *
        apply IHt3. eapply context_invariance...
        intros z Hafi. unfold update, t_update.
        destruct (eqb_stringP x2 z)...
        subst. rewrite false_eqb_string...
  -
    rename s into y1. rename s0 into y2.
    eapply T_Lcase...
    destruct (eqb_stringP x y1).
    +
      simpl.
      eapply context_invariance...
      subst.
      intros z Hafi. unfold update, t_update.
      destruct (eqb_stringP y1 z)...
    +
      destruct (eqb_stringP x y2).
      *
        eapply context_invariance...
        subst.
        intros z Hafi. unfold update, t_update.
        destruct (eqb_stringP y2 z)...
      *
        apply IHt3. eapply context_invariance...
        intros z Hafi. unfold update, t_update.
        destruct (eqb_stringP y1 z)...
        subst. rewrite false_eqb_string...
        destruct (eqb_stringP y2 z)...
        subst. rewrite false_eqb_string...


Admitted.

Definition manual_grade_for_substitution_preserves_typing : option (nat*string) := None.

Preservation

Exercise: 3 stars, standard (STLCE_preservation)

Complete the proof of preservation.

Theorem preservation : forall t t' T,
     empty |- t \in T ->
     t --> t' ->
     empty |- t' \in T.
Proof with eauto.
  intros t t' T HT.
  remember empty as Gamma. generalize dependent HeqGamma.
  generalize dependent t'.
  induction HT;
    intros t' HeqGamma HE; subst; inversion HE; subst...
  -
    
    inversion HE; subst...
    +
      
      apply substitution_preserves_typing with T1...
      inversion HT1...
  -
    inversion HT1; subst.
    eapply substitution_preserves_typing...
  -
    inversion HT1; subst.
    eapply substitution_preserves_typing...
  -
    +
      inversion HT1; subst.
      apply substitution_preserves_typing with (List T1)...
      apply substitution_preserves_typing with T1...


Admitted.

Definition manual_grade_for_preservation : option (nat*string) := None.