diff --git a/GLOSSARY.md b/GLOSSARY.md index f59a9de..83da797 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -2,6 +2,7 @@ | 英語 | 日本語 | | --- | --- | +| congruence | 合同 | | exporter | エクスポータ | | inductive | 帰納的対象(後ろにtypeもdeclaretionもつかない場合) | | recursor | 再帰子 | @@ -11,5 +12,7 @@ | 用語 | | --- | | large elimination | +| locally nameless | | Pollack consistency | +| proof irrelevance | | telescope | diff --git a/src/SUMMARY.md b/src/SUMMARY.md index adf993f..6fef779 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -20,7 +20,7 @@ - [宣言](./declarations/declarations.md) - [帰納型の知られざる一生](./declarations/inductive.md) - [型推論](./type_checking/type_inference.md) -- [Definitional Equality](./type_checking/definitional_equality.md) +- [定義上の同値](./type_checking/definitional_equality.md) - [Reduction](./type_checking/reduction.md) - [Open issues & future work](./future_work.md) - [Further reading](./further_reading.md) \ No newline at end of file diff --git a/src/type_checking/definitional_equality.md b/src/type_checking/definitional_equality.md index b246682..1603877 100644 --- a/src/type_checking/definitional_equality.md +++ b/src/type_checking/definitional_equality.md @@ -1,51 +1,123 @@ + +# 定義上の同値 + + + +定義上の同値は関数として実装されています。これは2つの式を受け取り、それらが Lean の理論の中で定義上等しい場合は `true` を、そうでない場合は `false` を返します。 + +カーネル内に置いて定義上の同値は重要なものですが、それは単に型チェックに必要だからです。またカーネル内部へ挑まない Lean ユーザにとっても定義上の同値は重要な概念です。というのも定義上の同値は Lean を書く上で比較的簡単に扱えるからです;定義上等しい `a` と `b` に対して、Lean は2つの式が等しいかどうかを判断するためにユーザからのプロンプトや追加の入力を必要としません。 + + + +定義上の同値の処理を実装には大きく分けて2つの部分があります。まず、定義が等しいかどうかをチェックするための個別のテストです。エンドユーザの視点から定義上の同値を理解することに興味がある読者にとっては、おそらくこれが知りたいことでしょう。 + +型チェッカを書くことに興味がある読者は、問題を扱いやすくするために個々のチェックが簡約やキャッシュに対してどのように組み合わされるかについても理解しておく必要があります;素朴に各チェックを実行し、それに従い簡約を行うことは受け入れがたいパフォーマンスとなってしまう可能性が高いです。 + + + +## ソートの同値 + + +2つの `Sort` 式が定義上等しいのは、それらが表すレベルがレベルの半順序関係の反対称律によって等しい場合です。 ``` defEq (Sort x) (Sort y): x ≤ y ∧ y ≤ x ``` + + +## 定数の同値 + +2つの `Const` 式が定義上等しいのは、名前が同一で、レベルが反対称律の下で等しい場合です。 + + +``` +defEq (Const n xs) (Const m ys): + n == m ∧ forall (x, y) in (xs, ys), antisymmEq x y + -- もし利用する `zip` がよしなにしてくれない場合は、xs と ys が同じ長さであることも主張します +``` + + + +## 束縛変数 + +locally nameless のような置換ベースの戦略を使用している場合(もし C++ や lean4lean の実装に従っているならあなたのことです)、束縛変数に遭遇するとエラーになります;というのも束縛変数はそれが引数を参照している場合、弱い簡約を行う中で置換されるか、pi またはラムダ式の定義上の同値チェックの一部として強い簡約を介して自由変数に置換されるべきです。 + + + +クロージャベースの実装では、束縛変数に対応する要素を調べ、それらが定義上等しいことを保証します。 + +## 自由変数 + + + +2つの自由変数が定義上等しいのは、同じ識別子(一意な ID か de Bruijn レベル)を持っている場合です。束縛子の型が等しいかどうかの検証は、(pi ・ラムダ式の定義上の同値チェックと同じように)自由変数が構築された場所で必ず行われたはずなので、今更再チェックする必要はありません。 ``` defEqFVar (id1, _) (id2, _): id1 == id2 ``` + +## 関数適用 + + + +2つの関数適用の式が定義上等しいのは、関数のコンポーネントと引数のコンポーネントが定義上等しい場合です。 ``` defEqApp (App f a) (App g b): @@ -55,7 +127,11 @@ defEqApp (App f a) (App g b): ## Pi + + +2つの Pi の式が定義上等しいのは、束縛子の型が定義上等しく、適切な自由変数を代入した後の本体の型が定義上等しい場合です。 ``` defEq (Pi s a) (Pi t b) @@ -67,9 +143,17 @@ defEq (Pi s a) (Pi t b) false ``` + + +## ラムダ式 + + +ラムダ式では Pi と同じチェックを用います: ``` defEq (Lambda s a) (Lambda t b) @@ -81,10 +165,19 @@ defEq (Lambda s a) (Lambda t b) false ``` + + +## 構造的なη + +Lean は2つの要素 `x` と `y` が両方ともある構造体型のインスタンスであり、一方のコンストラクタ引数と他方の射影されたフィールドを比較する以下の手順を使用してフィールドが定義上等しい場合、定義上の同値を認めます: + + +``` +defEqEtaStruct x y: + let (yF, yArgs) := unfoldApps y + if + yF is a constructor for an inductive type `T` + && `T` can be a struct + && yArgs.len == T.numParams + T.numFields + && defEq (infer x) (infer y) + then + forall i in 0..t.numFields, defEq Proj(i+T.numParams, x) yArgs[i+T.numParams] + + -- `T.numParams` をインデックスに追加しているのは、 + -- パラメータ以外の引き数のみをテストしたいからです。 + -- 推論される型が定義上等しいため、パラメータが defEq であることはすでに分かっています。 +``` + +より一般的な例である `[a, .., N] = [x, .., M]` の時の合同 `T.mk a .. N` = `T.mk x .. M` は、単純に `App` のチェックで処理できます。 + + + +## ユニットのようなものの同値 + +Lean では以下の条件の下にある2つの要素 `x: S p_0 .. p_N` と `y: T p_0 .. p_M` が定義上等しいとしています: + + + ++ `S` は帰納型である ++ `S` は添字を持たない ++ `S` はただ一つのコンストラクタを持ち、それが `S` のパラメータ `p_0 .. p_N` 以外に引数を取らないこと ++ 型 `S p_0 .. p_N` と `T p0 .. p_M` が定義上等しいこと + +直観的にこの定義上の同値は良いでしょう。なぜなら、これらの型の要素が伝えることのできる情報はすべて型に捕捉されており、私たちはこれらの型が定義上等しいことを要求しているからです。 + + + +## η 展開 ``` defEqEtaExpansion x y : bool := @@ -124,11 +260,23 @@ defEqEtaExpansion x y : bool := | _, _ => false ``` + + +右辺でラムダ式が作られており、この `(fun _ => $0) y` は明らかに `y` に簡約されますが、ラムダ式の束縛子を追加することで、 `x` と `y` が残りの定義上の同値の手続きによる一致の機会を与えます。 + +# proof irrelevance な同値 + + + +Lean は proof irrelevance な同値を定義上のものとして扱います。例えば、Lean の定義上の同値の手続きは `2 + 2 = 4` についての任意の2つの証明を定義上同値として扱います。 If a type `T` infers as `Sort 0`, we know it's a proof, because it is an element of `Prop` (remember that `Prop` is `Sort 0`). @@ -141,11 +289,23 @@ defEqByProofIrrelevance p q : defEq(S, T) ``` + +`p` が `A` 型の証明で、`q` が `B` 型の証明である場合、`A` が `B` と定義上等しいなら、`p` と `q` は proof irrelevance によって定義上等しいです。 + + + +## 整数(整数リテラル) + + +2つの整数リテラルが定義上等しいのは、それらが `Nat.zero` に簡約できる場合か、(`Nat.succ x`, `Nat.succ y`) へと簡約できる場合で、ここで `x` と `y` は定義上等しいです。 ``` match X, Y with @@ -157,24 +317,60 @@ match X, Y with | _, _ => false ``` + + +## 文字列リテラル `StringLit(s), App(String.mk, a)` + + +文字列リテラル `s` は `Const(String.mk, [])` の適用によって `List Char` に変換されます。Lean の `Char` 型は Unicode スカラー値を表現するために使用されるため、その整数表現は32ビットの符号なし整数です。 + + +例として、32ビットの符号なし整数 `111` と `107` に対応する2つの文字を使った文字列リテラル「ok」は次のように変換されます。 >(String.mk (((List.cons Char) (Char.ofNat.[] NatLit(111))) (((List.cons Char) (Char.ofNat NatLit(107))) (List.nil Char)))) + + +## Lazy delta 簡約 + +利用可能なカーネル実装は [簡約のヒント](./declarations.md#reducibility-hints) を使用して遅延的に定義を展開し、物事が有望に見えるときに一致をチェックする定義上の同値チェックの一部として「Lazy delta 簡約」手順を実装しています。これは定義が一致するかどうかをチェックする前に両方の式を完全に簡約するよりもはるかに効率的な戦略です。 + + + +`a` と `b` という2つの式があり、`a` が高さ10の定義の適用で、`b` が高さ12の定義の適用である場合、両者を完全に展開したりやみくもにどちらか片方を展開するのではなく、Lazy delta 手順は `b` を展開して `a` に近づけようとするより効率的なルートを取ります。 + +Lazy delta 処理が引数への `const` 式の適用である2つの式を見つけ、その `const` 式が同じ宣言を参照している場合、その式の合同(定義上等しい引数に適用される同じ定数であるかどうか)がチェックされます。合同チェックは `def_eq_args` の呼び出しが効果になる可能性があるため、独自のカーネルを書く読者にとって、これは失敗をキャッシュすることはパフォーマンス上重要な最適化であることがわかります。 + + + +## 文法上の同値(または構造体・ポインタ上の同値) + + -Two expressions are definitionally equal if they refer to exactly the same implementing object, as long as the type checker ensures that two objects are equal if and only if they are constructed from the same components (where the relevant constructors are those for Name, Level, and Expr). \ No newline at end of file +型チェッカが2つのオブジェクトが同じコンポーネント(Name・Level・Expr のコンストラクタが該当します)から構築される場合に限り等しいことを保証する限り、2つの式が全く同じ実装されたオブジェクトを参照する場合、これらは定義上等しくなります。