これまでの章では、項に対する三つの操作をブラックボックスとして扱ってきた。T-App と Freshen、そしてあらゆる計算ステップが使う代入。Π \Pi Π や宇宙を露わにするために使う弱頭正規化。Check が使う定義的等価である。この三つは信頼されたカーネルの一部であり、束縛変数の名前を正しく扱わなければならないので、カーネルのバグの大半はここに潜む。本章では最初の箱である代入を開け、同じ名前の問題が文脈にも現れることを示し、最後に、二つの項が束縛変数の名前を除いて等しいとはどういうことかを定義する。
自由変数と代入
Π \Pi Π と λ \lambda λ は変数を束縛 し、λ ( x : A ) . t \tLam{x}{A}{t} λ ( x : A ) . t において、t t t の中に現れる x x x は引数を指す。束縛子のスコープは本体だけであり、注釈 A A A の中では x x x は束縛されない。外側の Π \Pi Π や λ \lambda λ に束縛されていない変数の出現を自由 な出現と呼ぶ。
定義 11.1(自由変数).
F V ( S o r t u ) = ∅ F V ( Π ( x : A ) . B ) = F V ( A ) ∪ ( F V ( B ) ∖ { x } ) F V ( x ) = { x } F V ( λ ( x : A ) . t ) = F V ( A ) ∪ ( F V ( t ) ∖ { x } ) F V ( t s ) = F V ( t ) ∪ F V ( s ) F V ( c ) = ∅ \begin{array}{rclcrcl}
\FV(\Sort{u}) &=& \emptyset &\qquad& \FV(\tPi{x}{A}{B}) &=& \FV(A) \cup (\FV(B) \setminus \{x\}) \\[2pt]
\FV(x) &=& \{x\} && \FV(\tLam{x}{A}{t}) &=& \FV(A) \cup (\FV(t) \setminus \{x\}) \\[2pt]
\FV(t\;s) &=& \FV(t) \cup \FV(s) && \FV(c) &=& \emptyset
\end{array} FV ( Sort u ) FV ( x ) FV ( t s ) = = = ∅ { x } FV ( t ) ∪ FV ( s ) FV ( Π ( x : A ) . B ) FV ( λ ( x : A ) . t ) FV ( c ) = = = FV ( A ) ∪ ( FV ( B ) ∖ { x }) FV ( A ) ∪ ( FV ( t ) ∖ { x }) ∅
たとえば F V ( λ ( x : P ) . f x y ) = { P , f , y } \FV(\tLam{x}{P}{f\;x\;y}) = \{P, f, y\} FV ( λ ( x : P ) . f x y ) = { P , f , y } であり、F V ( Π ( P : S o r t 0 ) . P → P ) = ∅ \FV(\tPi{P}{\Sort{0}}{P \to P}) = \emptyset FV ( Π ( P : Sort 0 ) . P → P ) = ∅ である。後者は閉じた 項(closed term)である。定数は決して束縛されないので、自由変数には何も寄与しない。
エクスプローラは各束縛変数をその束縛子と同じ色で塗り、自由変数には下線を引き、変数にカーソルを合わせると束縛子が分かる。fun (x : A) => fun (x : A) => x のようなシャドーイングを含む項も試してみてほしい。
t t t の中の x x x の自由な出現を s s s で置き換えた結果を t [ x ↦ s ] \subst{t}{x}{s} t [ x ↦ s ] と書くが、束縛子があるときには注意が要る。たとえば ( λ ( y : A ) . x ) [ x ↦ y ] \subst{(\tLam{y}{A}{x})}{x}{y} ( λ ( y : A ) . x ) [ x ↦ y ] の本体は、「引数を無視して外側の x x x を返す」という意味である。ところが単純に x x x を y y y で置き換えると、自由だった y y y が束縛子に捕獲 され、恒等関数 λ ( y : A ) . y \tLam{y}{A}{y} λ ( y : A ) . y になってしまう。意味を保つには、代入する前に束縛子を改名しなければならない。
定義 11.2(捕獲回避代入). □ ∈ { Π , λ } \square \in \{\Pi, \lambda\} □ ∈ { Π , λ } とする。
x [ x ↦ s ] = s y [ x ↦ s ] = y ( y ≠ x ) S o r t u [ x ↦ s ] = S o r t u c [ x ↦ s ] = c ( t 1 t 2 ) [ x ↦ s ] = t 1 [ x ↦ s ] t 2 [ x ↦ s ] ( □ ( x : A ) . B ) [ x ↦ s ] = □ ( x : A [ x ↦ s ] ) . B ( x is shadowed) ( □ ( y : A ) . B ) [ x ↦ s ] = □ ( y : A [ x ↦ s ] ) . B [ x ↦ s ] ( y ≠ x , y ∉ F V ( s ) ) ( □ ( y : A ) . B ) [ x ↦ s ] = □ ( z : A [ x ↦ s ] ) . ( B [ y ↦ z ] ) [ x ↦ s ] ( y ≠ x , y ∈ F V ( s ) , z fresh ) \begin{array}{rcll}
\subst{x}{x}{s} &=& s \\[2pt]
\subst{y}{x}{s} &=& y & (y \neq x) \\[2pt]
\subst{\Sort{u}}{x}{s} &=& \Sort{u} \\[2pt]
\subst{c}{x}{s} &=& c \\[2pt]
\subst{(t_1\;t_2)}{x}{s} &=& \subst{t_1}{x}{s}\;\;\subst{t_2}{x}{s} \\[2pt]
\subst{(\square(x{:}A).\,B)}{x}{s} &=& \square(x{:}\subst{A}{x}{s}).\,B & \text{($x$ is shadowed)} \\[2pt]
\subst{(\square(y{:}A).\,B)}{x}{s} &=& \square(y{:}\subst{A}{x}{s}).\,\subst{B}{x}{s} & (y \neq x,\; y \notin \FV(s)) \\[2pt]
\subst{(\square(y{:}A).\,B)}{x}{s} &=& \square(z{:}\subst{A}{x}{s}).\,\subst{(\subst{B}{y}{z})}{x}{s} & (y \neq x,\; y \in \FV(s),\; z \text{ fresh})
\end{array} x [ x ↦ s ] y [ x ↦ s ] Sort u [ x ↦ s ] c [ x ↦ s ] ( t 1 t 2 ) [ x ↦ s ] ( □ ( x : A ) . B ) [ x ↦ s ] ( □ ( y : A ) . B ) [ x ↦ s ] ( □ ( y : A ) . B ) [ x ↦ s ] = = = = = = = = s y Sort u c t 1 [ x ↦ s ] t 2 [ x ↦ s ] □ ( x : A [ x ↦ s ]) . B □ ( y : A [ x ↦ s ]) . B [ x ↦ s ] □ ( z : A [ x ↦ s ]) . ( B [ y ↦ z ]) [ x ↦ s ] ( y = x ) ( x is shadowed) ( y = x , y ∈ / FV ( s )) ( y = x , y ∈ FV ( s ) , z fresh )
ここで「z z z が新鮮(fresh)」とは z ∉ F V ( s ) ∪ F V ( B ) ∪ { x } z \notin \FV(s) \cup \FV(B) \cup \{x\} z ∈ / FV ( s ) ∪ FV ( B ) ∪ { x } を意味する。
次の例は束縛子の各場合を一つずつ試すものである。
代入
結果
理由
( Π ( h : P ) . P ) [ P ↦ Q ] \subst{(\tPi{h}{P}{P})}{P}{Q} ( Π ( h : P ) . P ) [ P ↦ Q ]
Π ( h : Q ) . Q \tPi{h}{Q}{Q} Π ( h : Q ) . Q
捕獲は起こりえない。第 6 章の T-App のステップ
( λ ( x : A ) . x ) [ x ↦ s ] \subst{(\tLam{x}{A}{x})}{x}{s} ( λ ( x : A ) . x ) [ x ↦ s ]
λ ( x : A ) . x \tLam{x}{A}{x} λ ( x : A ) . x
x x x はシャドーイングされており、本体はそのまま
( λ ( y : x ) . y ) [ x ↦ s ] \subst{(\tLam{y}{x}{y})}{x}{s} ( λ ( y : x ) . y ) [ x ↦ s ]
λ ( y : s ) . y \tLam{y}{s}{y} λ ( y : s ) . y
注釈は y y y のスコープの外にある
( λ ( y : A ) . x y ) [ x ↦ f y ] \subst{(\tLam{y}{A}{x\;y})}{x}{f\;y} ( λ ( y : A ) . x y ) [ x ↦ f y ]
λ ( z : A ) . f y z \tLam{z}{A}{f\;y\;z} λ ( z : A ) . f y z
y ∈ F V ( f y ) y \in \FV(f\;y) y ∈ FV ( f y ) なので束縛子を z z z に改名する
最後の例では、置き換え項に含まれる自由な y y y は依然として外側の y y y を指し、束縛変数(今は z z z )は依然として引数を指す。改名しなければ結果は λ ( y : A ) . f y y \tLam{y}{A}{f\;y\;y} λ ( y : A ) . f y y となり、引数を二度渡す関数になってしまう。エクスプローラは二通りの代入を並べて実行し、結果に現れる y y y が、置き換え項由来の自由なものなのか、束縛子に捕獲されたものなのかは、色で区別できる。
定義 11.1 と 11.2 は src/substitution.rs の fv と subst である。subst の束縛子の場合を以下に示す。定義と比べて近道が一つ加わっており、x x x が本体に自由に出現しなければ本体をそのまま返す。これで走査を一回省けるし、結果は変わらない。
Term ::Pi (y, domain, body) | Term ::Lam (y, domain, body) => {
let substituted_domain = subst(domain, x, replacement);
let (renamed_binder, substituted_body) = if y == x || !fv(body).contains(x) {
(y.clone(), (**body).clone()) // shadowed, or nothing to do
} else if !fv(replacement).contains(y) {
(y.clone(), subst(body, x, replacement)) // no capture possible
} else {
let mut avoid = all_names(body); // capture: rename y to a fresh z
avoid.extend(all_names(domain));
avoid.extend(all_names(replacement));
avoid.insert(x.to_string());
let z = fresh(y, &avoid);
let body_with_z = subst(body, y, &var(&z));
(z, subst(&body_with_z, x, replacement))
};
// ... renamed_binder、substituted_domain、substituted_body から Pi または Lam を組み立て直す
}
fresh 関数は、名前が未使用になるまでヒントにプライムを付け加える。回避集合は all_names、すなわち自由な名前だけでなく関係する項に含まれるすべての名前であり、定義が要求する以上の範囲だが、そのほうが出力が読みやすい。
文脈における変数の捕獲
C-Ext の側条件 x ∉ d o m ( Γ ) x \notin \dom(\Gamma) x ∈ / dom ( Γ ) を落とし、多くのプログラミング言語と同じように、新しい仮定が古い仮定を単にシャドーイングするようにしたくなるかもしれないが、そうするとカーネルは不健全になる。次の項を考えよう。
t = λ ( x : S o r t 0 ) . λ ( y : x ) . λ ( x : S o r t 0 ) . y . t = \tLam{x}{\Sort{0}}{\tLam{y}{x}{\tLam{x}{\Sort{0}}{y}}}. t = λ ( x : Sort 0 ) . λ ( y : x ) . λ ( x : Sort 0 ) . y .
変数 y y y の型は外側の x x x であり、最も内側の λ \lambda λ は引数を無視するので、t t t の正しい型は次である。
Π ( x : S o r t 0 ) . Π ( y : x ) . Π ( x ′ : S o r t 0 ) . x . \tPi{x}{\Sort{0}}{\tPi{y}{x}{\tPi{x'}{\Sort{0}}{x}}}. Π ( x : Sort 0 ) . Π ( y : x ) . Π ( x ′ : Sort 0 ) . x .
F r e s h e n \Freshen Freshen のない型検査器は、文脈 x : S o r t 0 , y : x , x : S o r t 0 x : \Sort{0},\; y : x,\; x : \Sort{0} x : Sort 0 , y : x , x : Sort 0 の中で y y y を引き、その型である裸の名前 x x x を返すが、この時点で x x x は内側の束縛子を指しているため、型検査器が返すのは
Π ( x : S o r t 0 ) . Π ( y : x ) . Π ( x : S o r t 0 ) . x , \tPi{x}{\Sort{0}}{\tPi{y}{x}{\tPi{x}{\Sort{0}}{x}}}, Π ( x : Sort 0 ) . Π ( y : x ) . Π ( x : Sort 0 ) . x ,
となり、これは「任意の型の任意の要素から、あらゆる型の要素を作れる」と言っている。A : S o r t 0 A : \Sort{0} A : Sort 0 と a : A a : A a : A が与えられれば、項 t A a t\;A\;a t A a の型は Π ( B : S o r t 0 ) . B \tPi{B}{\Sort{0}}{B} Π ( B : Sort 0 ) . B 、つまり False の証明になる。そのような A A A と a a a は空の文脈にも存在する。A = Π ( P : S o r t 0 ) . P → P A = \tPi{P}{\Sort{0}}{P \to P} A = Π ( P : Sort 0 ) . P → P はそれ自身が命題であり、恒等関数 a = λ ( P : S o r t 0 ) . λ ( h : P ) . h a = \tLam{P}{\Sort{0}}{\tLam{h}{P}{h}} a = λ ( P : Sort 0 ) . λ ( h : P ) . h がそれを証明するからだ。
F r e s h e n \Freshen Freshen があれば、最も内側の束縛子は文脈に入る前に x ′ x' x ′ に改名される。文脈は x : S o r t 0 , y : x , x ′ : S o r t 0 x : \Sort{0},\; y : x,\; x' : \Sort{0} x : Sort 0 , y : x , x ′ : Sort 0 となり、y y y を引くと返ってくる x x x は依然として外側の束縛子を指し、I n f e r \Infer Infer は上に示した正しい型を返す。導出エクスプローラでこの改名の様子を確認でき、最も内側の T-Lam ノードの注記が改名を報告し、葉の文脈には x ′ x' x ′ が含まれている。
# カーネルは内側の x を x' に改名し、正しい型を推論する。
# 内側の x が外側の x をシャドーイングすることを許すカーネルは、
# この項を二つ目の文の証明として受け入れてしまう。二つ目の文は偽である。
goal forall (x : Sort 0), forall (y : x), forall (x' : Sort 0), x
exact fun (x : Sort 0) => fun (y : x) => fun (x : Sort 0) => y
qed
goal forall (x : Sort 0), forall (y : x), forall (x' : Sort 0), x'
exact fun (x : Sort 0) => fun (y : x) => fun (x : Sort 0) => y
qed
問題は代入の場合と同じで、名前が、別の意味を持つスコープへ移動してしまう。文脈がリストであれハッシュマップであれ、変数を名前で識別する表現はすべてこの影響を受ける。よく使われる対処法は三つある。
シャドーイングを一律に拒否するのが最も単純な選択肢だが、A → B A \to B A → B が使う予約名 _ は例外にして、決して文脈に加えないようにしなければならない。
文脈に入るときに改名する。F r e s h e n \Freshen Freshen がこれであり、本書の選択でもある。
束縛変数を位置で表す(de Bruijn インデックス [de Bruijn 1972])か、locally nameless 方式を使う。後者では束縛変数はインデックスで表し、自由変数は一意な識別子を持ち、Lean 4 は後者の方式を採る。
テストスイートには上の t t t を含め、カーネルがそれを拒否するか、正しい型を割り当てるかを確認すべきである。
このバグは書いてしまいやすく、普通の証明はすべて通ってしまうので気づきにくい。入れ子の束縛子で意図的に名前を使い回す項だけがこのバグを露わにする。カーネルのテストスイートには、正しい証明だけでなく、敵対的な項(adversarial terms)も必要である。
α 同値
束縛変数の名前には意味がなく、λ ( x : A ) . x \tLam{x}{A}{x} λ ( x : A ) . x と λ ( y : A ) . y \tLam{y}{A}{y} λ ( y : A ) . y は同じ関数であり、Π ( P : S o r t 0 ) . P → P \tPi{P}{\Sort{0}}{P \to P} Π ( P : Sort 0 ) . P → P と Π ( Q : S o r t 0 ) . Q → Q \tPi{Q}{\Sort{0}}{Q \to Q} Π ( Q : Sort 0 ) . Q → Q は同じ命題である。束縛変数の名前だけが異なる項を α 同値 (alpha-equivalent)であるという。カーネルは型を単なる構文的な等しさで比較するわけにはいかず、それでは後者の組が拒否されてしまう。
定義 11.3(α 同値). = α \alphaeq = α は、□ ∈ { Π , λ } \square \in \{\Pi, \lambda\} □ ∈ { Π , λ } について次を含む最小の合同関係である。
□ ( x : A ) . B = α □ ( y : A ) . B [ x ↦ y ] whenever y ∉ F V ( B ) . \square(x{:}A).\,B \;\alphaeq\; \square(y{:}A).\,\subst{B}{x}{y}
\qquad \text{whenever } y \notin \FV(B). □ ( x : A ) . B = α □ ( y : A ) . B [ x ↦ y ] whenever y ∈ / FV ( B ) .
(合同関係 (congruence)とは、すべての項構成子で保たれる同値関係である。部分どうしが関係にあれば、全体どうしも関係にある。)この定義は述べるのは簡単だが、どの名前を選ぶべきかを言っていないので、そのままでは実行できない。実装は代わりに両方の項を足並みをそろえてたどり、それぞれの側で、ここまでに通過した束縛子のリストを覚えておく。束縛変数はこうして、名前ではなくどの 束縛子を指しているかで識別される。
この走査を記述するために、E 1 ; E 2 ⊢ s = α t E_1; E_2 \vdash s \alphaeq t E 1 ; E 2 ⊢ s = α t という形の判断を使う。E 1 E_1 E 1 と E 2 E_2 E 2 は名前のリストで、E 1 E_1 E 1 は s s s を囲む束縛子を外側から内側の順に記録し、E 2 E_2 E 2 は t t t を囲む束縛子を記録する。この判断は「束縛子 E 1 E_1 E 1 の内側に置かれた s s s と、束縛子 E 2 E_2 E 2 の内側に置かれた t t t は、束縛変数の名前を除いて同じ項である」と読む。リストが二つ必要なのは、対応する束縛子に二つの項が異なる名前を使うかもしれないからだ。たとえば λ ( x : A ) . λ ( y : B ) . x \tLam{x}{A}{\tLam{y}{B}{x}} λ ( x : A ) . λ ( y : B ) . x と λ ( a : A ) . λ ( b : B ) . a \tLam{a}{A}{\tLam{b}{B}{a}} λ ( a : A ) . λ ( b : B ) . a の比較では、各側で二つの束縛子を通過して
x , y ; a , b ⊢ x = α a , x, y;\; a, b \vdash x \alphaeq a, x , y ; a , b ⊢ x = α a ,
に到達し、x x x と a a a はどちらも二つの束縛子のうち外側を指すので、これは成り立つ。二つの項全体の比較は、空のリスト ⋅ ; ⋅ ⊢ s = α t \cdot\,;\cdot \vdash s \alphaeq t ⋅ ; ⋅ ⊢ s = α t から始まる。
「同じ束縛子」を正確に言うために、i d x ( E , x ) \mathrm{idx}(E, x) idx ( E , x ) を、E E E における x x x の最後の 出現の位置を右から 0 0 0 始まりで数えたものとし、x ∉ E x \notin E x ∈ / E なら未定義とする。上の例では i d x ( ( x , y ) , x ) = i d x ( ( a , b ) , a ) = 1 \mathrm{idx}((x, y), x) = \mathrm{idx}((a, b), a) = 1 idx (( x , y ) , x ) = idx (( a , b ) , a ) = 1 である。最後の出現を取ることでシャドーイングが扱え、λ ( x : A ) . λ ( x : B ) . x \tLam{x}{A}{\tLam{x}{B}{x}} λ ( x : A ) . λ ( x : B ) . x の本体は内側の x x x を指す。
図 11.1. アルゴリズム的 α 同値。定理 11.4 により、二つの項 s s s と t t t が α 同値であることと ⋅ ; ⋅ ⊢ s = α t \cdot\,;\cdot \vdash s \alphaeq t ⋅ ; ⋅ ⊢ s = α t とは同値である。
E 1 ; E 2 ⊢ S o r t u = α S o r t u ; ( α -Sort) E 1 ; E 2 ⊢ c = α c ; ( α -Const) \dfrac{}{E_1; E_2 \vdash \Sort{u} \alphaeq \Sort{u}}\rulename{$\alpha$-Sort}
\qquad
\dfrac{}{E_1; E_2 \vdash c \alphaeq c}\rulename{$\alpha$-Const} E 1 ; E 2 ⊢ Sort u = α Sort u ; ( α -Sort) E 1 ; E 2 ⊢ c = α c ; ( α -Const)
i d x ( E 1 , x ) = i d x ( E 2 , y ) = i E 1 ; E 2 ⊢ x = α y ; ( α -Bound) x ∉ E 1 x ∉ E 2 E 1 ; E 2 ⊢ x = α x ; ( α -Free) \dfrac{\mathrm{idx}(E_1, x) = \mathrm{idx}(E_2, y) = i}{E_1; E_2 \vdash x \alphaeq y}\rulename{$\alpha$-Bound}
\qquad
\dfrac{x \notin E_1 \qquad x \notin E_2}{E_1; E_2 \vdash x \alphaeq x}\rulename{$\alpha$-Free} E 1 ; E 2 ⊢ x = α y idx ( E 1 , x ) = idx ( E 2 , y ) = i ; ( α -Bound) E 1 ; E 2 ⊢ x = α x x ∈ / E 1 x ∈ / E 2 ; ( α -Free)
E 1 ; E 2 ⊢ A = α A ′ E 1 , x ; E 2 , y ⊢ B = α B ′ E 1 ; E 2 ⊢ Π ( x : A ) . B = α Π ( y : A ′ ) . B ′ ; ( α -Pi) \dfrac{E_1; E_2 \vdash A \alphaeq A' \qquad E_1, x;\; E_2, y \vdash B \alphaeq B'}{E_1; E_2 \vdash \tPi{x}{A}{B} \alphaeq \tPi{y}{A'}{B'}}\rulename{$\alpha$-Pi} E 1 ; E 2 ⊢ Π ( x : A ) . B = α Π ( y : A ′ ) . B ′ E 1 ; E 2 ⊢ A = α A ′ E 1 , x ; E 2 , y ⊢ B = α B ′ ; ( α -Pi)
E 1 ; E 2 ⊢ A = α A ′ E 1 , x ; E 2 , y ⊢ t = α t ′ E 1 ; E 2 ⊢ λ ( x : A ) . t = α λ ( y : A ′ ) . t ′ ; ( α -Lam) E 1 ; E 2 ⊢ t 1 = α t 1 ′ E 1 ; E 2 ⊢ t 2 = α t 2 ′ E 1 ; E 2 ⊢ t 1 t 2 = α t 1 ′ t 2 ′ ; ( α -App) \dfrac{E_1; E_2 \vdash A \alphaeq A' \qquad E_1, x;\; E_2, y \vdash t \alphaeq t'}{E_1; E_2 \vdash \tLam{x}{A}{t} \alphaeq \tLam{y}{A'}{t'}}\rulename{$\alpha$-Lam}
\qquad
\dfrac{E_1; E_2 \vdash t_1 \alphaeq t_1' \qquad E_1; E_2 \vdash t_2 \alphaeq t_2'}{E_1; E_2 \vdash t_1\;t_2 \alphaeq t_1'\;t_2'}\rulename{$\alpha$-App} E 1 ; E 2 ⊢ λ ( x : A ) . t = α λ ( y : A ′ ) . t ′ E 1 ; E 2 ⊢ A = α A ′ E 1 , x ; E 2 , y ⊢ t = α t ′ ; ( α -Lam) E 1 ; E 2 ⊢ t 1 t 2 = α t 1 ′ t 2 ′ E 1 ; E 2 ⊢ t 1 = α t 1 ′ E 1 ; E 2 ⊢ t 2 = α t 2 ′ ; ( α -App)
自由変数は名前で比較し(α \alpha α -Free)、束縛変数は位置で比較し(α \alpha α -Bound)、定数は名前で比較する(α \alpha α -Const)。一方で束縛され他方で自由な変数は何とも等しくならないので、その場合の規則はない。両側は束縛子を一緒に下っていくので、E 1 E_1 E 1 と E 2 E_2 E 2 の長さは常に等しいので、右から数えた位置の比較は左から数えた位置の比較と同じであり、実装は各リストを名前から束縛の深さへの写像として保持してもよい。ただし束縛子を抜けるときには以前の項目を復元する必要がある。名前の代わりに位置をあらゆる場所で 使うと de Bruijn 表現 になり、そこでは α 同値な項は構文的に同一である。
例
第 6 章の最初の例は、推論した型と宣言された型の比較で終わった。ここで P → P P \to P P → P は Π ( _ : P ) . P \tPi{\_}{P}{P} Π ( _ : P ) . P の略記である。
⋅ ; ⋅ ⊢ Π ( P : S o r t 0 ) . Π ( h : P ) . P = α Π ( P : S o r t 0 ) . Π ( _ : P ) . P \cdot\,;\cdot \vdash \tPi{P}{\Sort{0}}{\tPi{h}{P}{P}} \alphaeq \tPi{P}{\Sort{0}}{\tPi{\_}{P}{P}} ⋅ ; ⋅ ⊢ Π ( P : Sort 0 ) . Π ( h : P ) . P = α Π ( P : Sort 0 ) . Π ( _ : P ) . P α \alpha α -Pi
⋅ ; ⋅ ⊢ S o r t 0 = α S o r t 0 \cdot\,;\cdot \vdash \Sort{0} \alphaeq \Sort{0} ⋅ ; ⋅ ⊢ Sort 0 = α Sort 0 :成立 α \alpha α -Sort
P ; P ⊢ Π ( h : P ) . P = α Π ( _ : P ) . P P;\, P \vdash \tPi{h}{P}{P} \alphaeq \tPi{\_}{P}{P} P ; P ⊢ Π ( h : P ) . P = α Π ( _ : P ) . P α \alpha α -Pi
P ; P ⊢ P = α P P;\, P \vdash P \alphaeq P P ; P ⊢ P = α P :成立。どちらもインデックスは 0 0 0 α \alpha α -Bound
P , h ; P , _ ⊢ P = α P P, h;\; P, \_ \vdash P \alphaeq P P , h ; P , _ ⊢ P = α P :成立。どちらもインデックスは 1 1 1 α \alpha α -Bound
名前 h h h と _ \_ _ は異なるが、どちらも一度も引かれないので問題にならない。第 6 章の誤った証明は、代わりに最後のステップで失敗する。
⋅ ; ⋅ ⊢ Π ( P : S o r t 0 ) . Π ( h : P ) . S o r t 0 = α Π ( P : S o r t 0 ) . Π ( _ : P ) . P \cdot\,;\cdot \vdash \tPi{P}{\Sort{0}}{\tPi{h}{P}{\Sort{0}}} \alphaeq \tPi{P}{\Sort{0}}{\tPi{\_}{P}{P}} ⋅ ; ⋅ ⊢ Π ( P : Sort 0 ) . Π ( h : P ) . Sort 0 = α Π ( P : Sort 0 ) . Π ( _ : P ) . P α \alpha α -Pi
⋅ ; ⋅ ⊢ S o r t 0 = α S o r t 0 \cdot\,;\cdot \vdash \Sort{0} \alphaeq \Sort{0} ⋅ ; ⋅ ⊢ Sort 0 = α Sort 0 :成立 α \alpha α -Sort
P ; P ⊢ Π ( h : P ) . S o r t 0 = α Π ( _ : P ) . P P;\, P \vdash \tPi{h}{P}{\Sort{0}} \alphaeq \tPi{\_}{P}{P} P ; P ⊢ Π ( h : P ) . Sort 0 = α Π ( _ : P ) . P α \alpha α -Pi
P ; P ⊢ P = α P P;\, P \vdash P \alphaeq P P ; P ⊢ P = α P :成立 α \alpha α -Bound
P , h ; P , _ ⊢ S o r t 0 = α P P, h;\; P, \_ \vdash \Sort{0} \alphaeq P P , h ; P , _ ⊢ Sort 0 = α P :適用できる規則がない
位置は、名前の不注意な比較では混同してしまう項も区別する。λ ( x : A ) . λ ( y : A ) . x \tLam{x}{A}{\tLam{y}{A}{x}} λ ( x : A ) . λ ( y : A ) . x と λ ( a : A ) . λ ( b : A ) . b \tLam{a}{A}{\tLam{b}{A}{b}} λ ( a : A ) . λ ( b : A ) . b は α 同値ではなく 、本体の比較は x , y ; a , b ⊢ x = α b x, y;\, a, b \vdash x \alphaeq b x , y ; a , b ⊢ x = α b に到達して、i d x \mathrm{idx} idx は x x x に 1 1 1 、b b b に 0 0 0 を与える。逆に λ ( x : A ) . λ ( x : A ) . x \tLam{x}{A}{\tLam{x}{A}{x}} λ ( x : A ) . λ ( x : A ) . x と λ ( a : A ) . λ ( b : A ) . b \tLam{a}{A}{\tLam{b}{A}{b}} λ ( a : A ) . λ ( b : A ) . b は α 同値であり 、( x , x ) (x, x) ( x , x ) における x x x の最後の出現のインデックスは 0 0 0 で、( a , b ) (a, b) ( a , b ) における b b b と同じである。
図 11.1 の規則は定義 11.3 とは別の定義なので、両者が一致することを確かめなければならない。
定理 11.4(アルゴリズム的 α 同値の正しさ). すべての項 s s s と t t t について、定義 11.3 の意味で s = α t s \alphaeq t s = α t であることと、⋅ ; ⋅ ⊢ s = α t \cdot\,;\cdot \vdash s \alphaeq t ⋅ ; ⋅ ⊢ s = α t が図 11.1 の規則で導出できることとは同値である。
証明の要点は、どちらの定義も「s s s と t t t が同じ de Bruijn 表現を持つ」と言い換えられることである。アルゴリズムの規則は de Bruijn インデックスと同じ方法で位置を比較しているので、この言い換えはほぼ直接に分かる。一方、定義 11.3 については、束縛変数を改名しても de Bruijn 表現が変わらないことを示す必要がある。完全な証明は 付録 B にある。
図 11.1 は src/substitution.rs の関数 alpha である。二つの束縛子リストは Vec<String> で、本体への再帰呼び出しの前後で push と pop を行う。binder_distance が i d x \mathrm{idx} idx にあたり、リストを末尾から探索するので、その名前を持つ最も近い外側の束縛子までの距離が最初に見つかる。
(Term ::Var (x), Term ::Var (y)) => {
match (binder_distance(bound_a, x), binder_distance(bound_b, y)) {
(Some (i), Some (j)) => i == j, // alpha-Bound
(None , None ) => x == y, // alpha-Free
_ => false , // bound on one side only
}
}
演習
( λ ( y : A ) . λ ( x : B ) . x y ) [ x ↦ y ] \subst{(\tLam{y}{A}{\tLam{x}{B}{x\;y}})}{x}{y} ( λ ( y : A ) . λ ( x : B ) . x y ) [ x ↦ y ] を一歩ずつ計算せよ。
解答
外側の束縛子は y y y で、これは置き換え項 y y y に自由に現れるので、捕獲の場合にあたる。y y y を新鮮な z z z に改名して本体 λ ( x : B ) . x z \tLam{x}{B}{x\;z} λ ( x : B ) . x z を得てから代入する。内側の束縛子は置き換え対象の変数 x x x なので、内側の本体には手を付けない。結果は λ ( z : A ) . λ ( x : B ) . x z \tLam{z}{A}{\tLam{x}{B}{x\;z}} λ ( z : A ) . λ ( x : B ) . x z である。内側の x x x はもともと自由な出現ではなかったので、実際には何も置き換わっておらず、唯一の効果は、捕獲回避が強いた改名である。
λ ( x : A ) . λ ( y : A ) . y x \tLam{x}{A}{\tLam{y}{A}{y\;x}} λ ( x : A ) . λ ( y : A ) . y x と λ ( y : A ) . λ ( x : A ) . x y \tLam{y}{A}{\tLam{x}{A}{x\;y}} λ ( y : A ) . λ ( x : A ) . x y が α 同値かどうかを、図 11.1 を実行して判定せよ。
解答
同値である。α \alpha α -Lam を二回適用すると、束縛子のリストは x , y x, y x , y と y , x y, x y , x 、本体は y x y\;x y x と x y x\;y x y になる。α \alpha α -App で y y y と x x x を比べるとインデックスは 0 0 0 と 0 0 0 、続いて x x x と y y y を比べると 1 1 1 と 1 1 1 になる。名前は入れ替わっていても、位置が一致するので同値である。
覚えておくこと
代入は自由な出現だけを置き換え、置き換え項が束縛子の名前を含むとき(捕獲)は、束縛子を改名しなければならない。
同じ捕獲の問題は文脈でも起こり、束縛子が文脈の項目をシャドーイングすることを許すカーネルは False を証明できてしまうが、Freshen がこれを防ぐ。
α 同値は、束縛変数をその束縛子の位置で、自由変数を名前で比較する。図 11.1 がそのアルゴリズムであり、定理 11.4 はそれが定義と一致することを述べる。
三つとも src/substitution.rs にあり、subst、fv、alpha である。