h5i

第 11 章

代入、捕獲、α 同値

これまでの章では、項に対する三つの操作をブラックボックスとして扱ってきた。T-App と Freshen、そしてあらゆる計算ステップが使う代入。Π\Pi や宇宙を露わにするために使う弱頭正規化。Check が使う定義的等価である。この三つは信頼されたカーネルの一部であり、束縛変数の名前を正しく扱わなければならないので、カーネルのバグの大半はここに潜む。本章では最初の箱である代入を開け、同じ名前の問題が文脈にも現れることを示し、最後に、二つの項が束縛変数の名前を除いて等しいとはどういうことかを定義する。

自由変数と代入

Π\Pi と λ\lambda は変数を束縛し、λ(x:A). t\tLam{x}{A}{t} において、tt の中に現れる xx は引数を指す。束縛子のスコープは本体だけであり、注釈 AA の中では xx は束縛されない。外側の Π\Pi や λ\lambda に束縛されていない変数の出現を自由な出現と呼ぶ。

定義 11.1(自由変数).

FV(Sort u)=∅FV(Π(x:A). B)=FV(A)∪(FV(B)∖{x})FV(x)={x}FV(λ(x:A). t)=FV(A)∪(FV(t)∖{x})FV(t  s)=FV(t)∪FV(s)FV(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(λ(x:P). f  x  y)={P,f,y}\FV(\tLam{x}{P}{f\;x\;y}) = \{P, f, y\} であり、FV(Π(P:Sort 0). P→P)=∅\FV(\tPi{P}{\Sort{0}}{P \to P}) = \emptyset である。後者は閉じた項(closed term)である。定数は決して束縛されないので、自由変数には何も寄与しない。

エクスプローラは各束縛変数をその束縛子と同じ色で塗り、自由変数には下線を引き、変数にカーソルを合わせると束縛子が分かる。fun (x : A) => fun (x : A) => x のようなシャドーイングを含む項も試してみてほしい。

tt の中の xx の自由な出現を ss で置き換えた結果を t[x↦s]\subst{t}{x}{s} と書くが、束縛子があるときには注意が要る。たとえば (λ(y:A). x)[x↦y]\subst{(\tLam{y}{A}{x})}{x}{y} の本体は、「引数を無視して外側の xx を返す」という意味である。ところが単純に xx を yy で置き換えると、自由だった yy が束縛子に捕獲され、恒等関数 λ(y:A). y\tLam{y}{A}{y} になってしまう。意味を保つには、代入する前に束縛子を改名しなければならない。

定義 11.2(捕獲回避代入). □∈{Π,λ}\square \in \{\Pi, \lambda\} とする。

x[x↦s]=sy[x↦s]=y(y≠x)Sort u[x↦s]=Sort uc[x↦s]=c(t1  t2)[x↦s]=t1[x↦s]    t2[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∉FV(s))(□(y:A). B)[x↦s]=□(z:A[x↦s]). (B[y↦z])[x↦s](y≠x,  y∈FV(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}

ここで「zz が新鮮(fresh)」とは z∉FV(s)∪FV(B)∪{x}z \notin \FV(s) \cup \FV(B) \cup \{x\} を意味する。

次の例は束縛子の各場合を一つずつ試すものである。

代入 結果 理由
(Π(h:P). P)[P↦Q]\subst{(\tPi{h}{P}{P})}{P}{Q} Π(h:Q). Q\tPi{h}{Q}{Q} 捕獲は起こりえない。第 6 章の T-App のステップ
(λ(x:A). x)[x↦s]\subst{(\tLam{x}{A}{x})}{x}{s} λ(x:A). x\tLam{x}{A}{x} xx はシャドーイングされており、本体はそのまま
(λ(y:x). y)[x↦s]\subst{(\tLam{y}{x}{y})}{x}{s} λ(y:s). y\tLam{y}{s}{y} 注釈は yy のスコープの外にある
(λ(y:A). x  y)[x↦f  y]\subst{(\tLam{y}{A}{x\;y})}{x}{f\;y} λ(z:A). f  y  z\tLam{z}{A}{f\;y\;z} y∈FV(f  y)y \in \FV(f\;y) なので束縛子を zz に改名する

最後の例では、置き換え項に含まれる自由な yy は依然として外側の yy を指し、束縛変数(今は zz)は依然として引数を指す。改名しなければ結果は λ(y:A). f  y  y\tLam{y}{A}{f\;y\;y} となり、引数を二度渡す関数になってしまう。エクスプローラは二通りの代入を並べて実行し、結果に現れる yy が、置き換え項由来の自由なものなのか、束縛子に捕獲されたものなのかは、色で区別できる。

文脈における変数の捕獲

C-Ext の側条件 x∉dom(Γ)x \notin \dom(\Gamma) を落とし、多くのプログラミング言語と同じように、新しい仮定が古い仮定を単にシャドーイングするようにしたくなるかもしれないが、そうするとカーネルは不健全になる。次の項を考えよう。

t=λ(x:Sort 0). λ(y:x). λ(x:Sort 0). y.t = \tLam{x}{\Sort{0}}{\tLam{y}{x}{\tLam{x}{\Sort{0}}{y}}}.

変数 yy の型は外側の xx であり、最も内側の λ\lambda は引数を無視するので、tt の正しい型は次である。

Π(x:Sort 0). Π(y:x). Π(x′:Sort 0). x.\tPi{x}{\Sort{0}}{\tPi{y}{x}{\tPi{x'}{\Sort{0}}{x}}}.

Freshen\Freshen のない型検査器は、文脈 x:Sort 0,  y:x,  x:Sort 0x : \Sort{0},\; y : x,\; x : \Sort{0} の中で yy を引き、その型である裸の名前 xx を返すが、この時点で xx は内側の束縛子を指しているため、型検査器が返すのは

Π(x:Sort 0). Π(y:x). Π(x:Sort 0). x,\tPi{x}{\Sort{0}}{\tPi{y}{x}{\tPi{x}{\Sort{0}}{x}}},

となり、これは「任意の型の任意の要素から、あらゆる型の要素を作れる」と言っている。A:Sort 0A : \Sort{0} と a:Aa : A が与えられれば、項 t  A  at\;A\;a の型は Π(B:Sort 0). B\tPi{B}{\Sort{0}}{B}、つまり False の証明になる。そのような AA と aa は空の文脈にも存在する。A=Π(P:Sort 0). P→PA = \tPi{P}{\Sort{0}}{P \to P} はそれ自身が命題であり、恒等関数 a=λ(P:Sort 0). λ(h:P). ha = \tLam{P}{\Sort{0}}{\tLam{h}{P}{h}} がそれを証明するからだ。

Freshen\Freshen があれば、最も内側の束縛子は文脈に入る前に x′x' に改名される。文脈は x:Sort 0,  y:x,  x′:Sort 0x : \Sort{0},\; y : x,\; x' : \Sort{0} となり、yy を引くと返ってくる xx は依然として外側の束縛子を指し、Infer\Infer は上に示した正しい型を返す。導出エクスプローラでこの改名の様子を確認でき、最も内側の T-Lam ノードの注記が改名を報告し、葉の文脈には 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

問題は代入の場合と同じで、名前が、別の意味を持つスコープへ移動してしまう。文脈がリストであれハッシュマップであれ、変数を名前で識別する表現はすべてこの影響を受ける。よく使われる対処法は三つある。

  1. シャドーイングを一律に拒否するのが最も単純な選択肢だが、A→BA \to B が使う予約名 _ は例外にして、決して文脈に加えないようにしなければならない。
  2. 文脈に入るときに改名する。Freshen\Freshen がこれであり、本書の選択でもある。
  3. 束縛変数を位置で表す(de Bruijn インデックス [de Bruijn 1972])か、locally nameless 方式を使う。後者では束縛変数はインデックスで表し、自由変数は一意な識別子を持ち、Lean 4 は後者の方式を採る。

テストスイートには上の tt を含め、カーネルがそれを拒否するか、正しい型を割り当てるかを確認すべきである。

α 同値

束縛変数の名前には意味がなく、λ(x:A). x\tLam{x}{A}{x} と λ(y:A). y\tLam{y}{A}{y} は同じ関数であり、Π(P:Sort 0). P→P\tPi{P}{\Sort{0}}{P \to P} と Π(Q:Sort 0). Q→Q\tPi{Q}{\Sort{0}}{Q \to Q} は同じ命題である。束縛変数の名前だけが異なる項を α 同値(alpha-equivalent)であるという。カーネルは型を単なる構文的な等しさで比較するわけにはいかず、それでは後者の組が拒否されてしまう。

定義 11.3(α 同値). =α\alphaeq は、□∈{Π,λ}\square \in \{\Pi, \lambda\} について次を含む最小の合同関係である。

□(x:A). B  =α  □(y:A). B[x↦y]whenever y∉FV(B).\square(x{:}A).\,B \;\alphaeq\; \square(y{:}A).\,\subst{B}{x}{y} \qquad \text{whenever } y \notin \FV(B).

(合同関係(congruence)とは、すべての項構成子で保たれる同値関係である。部分どうしが関係にあれば、全体どうしも関係にある。)この定義は述べるのは簡単だが、どの名前を選ぶべきかを言っていないので、そのままでは実行できない。実装は代わりに両方の項を足並みをそろえてたどり、それぞれの側で、ここまでに通過した束縛子のリストを覚えておく。束縛変数はこうして、名前ではなくどの束縛子を指しているかで識別される。

この走査を記述するために、E1;E2⊢s=αtE_1; E_2 \vdash s \alphaeq t という形の判断を使う。E1E_1 と E2E_2 は名前のリストで、E1E_1 は ss を囲む束縛子を外側から内側の順に記録し、E2E_2 は tt を囲む束縛子を記録する。この判断は「束縛子 E1E_1 の内側に置かれた ss と、束縛子 E2E_2 の内側に置かれた tt は、束縛変数の名前を除いて同じ項である」と読む。リストが二つ必要なのは、対応する束縛子に二つの項が異なる名前を使うかもしれないからだ。たとえば λ(x:A). λ(y:B). x\tLam{x}{A}{\tLam{y}{B}{x}} と λ(a:A). λ(b:B). a\tLam{a}{A}{\tLam{b}{B}{a}} の比較では、各側で二つの束縛子を通過して

x,y;  a,b⊢x=αa,x, y;\; a, b \vdash x \alphaeq a,

に到達し、xx と aa はどちらも二つの束縛子のうち外側を指すので、これは成り立つ。二つの項全体の比較は、空のリスト ⋅ ;⋅⊢s=αt\cdot\,;\cdot \vdash s \alphaeq t から始まる。

「同じ束縛子」を正確に言うために、idx(E,x)\mathrm{idx}(E, x) を、EE における xx の最後の出現の位置を右から 00 始まりで数えたものとし、x∉Ex \notin E なら未定義とする。上の例では idx((x,y),x)=idx((a,b),a)=1\mathrm{idx}((x, y), x) = \mathrm{idx}((a, b), a) = 1 である。最後の出現を取ることでシャドーイングが扱え、λ(x:A). λ(x:B). x\tLam{x}{A}{\tLam{x}{B}{x}} の本体は内側の xx を指す。

図 11.1. アルゴリズム的 α 同値。定理 11.4 により、二つの項 ss と tt が α 同値であることと ⋅ ;⋅⊢s=αt\cdot\,;\cdot \vdash s \alphaeq t とは同値である。

E1;E2⊢Sort u=αSort u;(α-Sort)E1;E2⊢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}
idx(E1,x)=idx(E2,y)=iE1;E2⊢x=αy;(α-Bound)x∉E1x∉E2E1;E2⊢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}
E1;E2⊢A=αA′E1,x;  E2,y⊢B=αB′E1;E2⊢Π(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}
E1;E2⊢A=αA′E1,x;  E2,y⊢t=αt′E1;E2⊢λ(x:A). t=αλ(y:A′). t′;(α-Lam)E1;E2⊢t1=αt1′E1;E2⊢t2=αt2′E1;E2⊢t1  t2=αt1′  t2′;(α-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}

自由変数は名前で比較し(α\alpha-Free)、束縛変数は位置で比較し(α\alpha-Bound)、定数は名前で比較する(α\alpha-Const)。一方で束縛され他方で自由な変数は何とも等しくならないので、その場合の規則はない。両側は束縛子を一緒に下っていくので、E1E_1 と E2E_2 の長さは常に等しいので、右から数えた位置の比較は左から数えた位置の比較と同じであり、実装は各リストを名前から束縛の深さへの写像として保持してもよい。ただし束縛子を抜けるときには以前の項目を復元する必要がある。名前の代わりに位置をあらゆる場所で使うと de Bruijn 表現になり、そこでは α 同値な項は構文的に同一である。

例

第 6 章の最初の例は、推論した型と宣言された型の比較で終わった。ここで P→PP \to P は Π(_:P). P\tPi{\_}{P}{P} の略記である。

  • ⋅ ;⋅⊢Π(P:Sort 0). Π(h:P). P=αΠ(P:Sort 0). Π(_:P). P\cdot\,;\cdot \vdash \tPi{P}{\Sort{0}}{\tPi{h}{P}{P}} \alphaeq \tPi{P}{\Sort{0}}{\tPi{\_}{P}{P}} α\alpha-Pi
    • ⋅ ;⋅⊢Sort 0=αSort 0\cdot\,;\cdot \vdash \Sort{0} \alphaeq \Sort{0}:成立 α\alpha-Sort
    • P; P⊢Π(h:P). P=αΠ(_:P). PP;\, P \vdash \tPi{h}{P}{P} \alphaeq \tPi{\_}{P}{P} α\alpha-Pi
      • P; P⊢P=αPP;\, P \vdash P \alphaeq P:成立。どちらもインデックスは 00 α\alpha-Bound
      • P,h;  P,_⊢P=αPP, h;\; P, \_ \vdash P \alphaeq P:成立。どちらもインデックスは 11 α\alpha-Bound

名前 hh と _\_ は異なるが、どちらも一度も引かれないので問題にならない。第 6 章の誤った証明は、代わりに最後のステップで失敗する。

  • ⋅ ;⋅⊢Π(P:Sort 0). Π(h:P). Sort 0=αΠ(P:Sort 0). Π(_:P). P\cdot\,;\cdot \vdash \tPi{P}{\Sort{0}}{\tPi{h}{P}{\Sort{0}}} \alphaeq \tPi{P}{\Sort{0}}{\tPi{\_}{P}{P}} α\alpha-Pi
    • ⋅ ;⋅⊢Sort 0=αSort 0\cdot\,;\cdot \vdash \Sort{0} \alphaeq \Sort{0}:成立 α\alpha-Sort
    • P; P⊢Π(h:P). Sort 0=αΠ(_:P). PP;\, P \vdash \tPi{h}{P}{\Sort{0}} \alphaeq \tPi{\_}{P}{P} α\alpha-Pi
      • P; P⊢P=αPP;\, P \vdash P \alphaeq P:成立 α\alpha-Bound
      • P,h;  P,_⊢Sort 0=αPP, h;\; P, \_ \vdash \Sort{0} \alphaeq P:適用できる規則がない

位置は、名前の不注意な比較では混同してしまう項も区別する。λ(x:A). λ(y:A). x\tLam{x}{A}{\tLam{y}{A}{x}} と λ(a:A). λ(b:A). b\tLam{a}{A}{\tLam{b}{A}{b}} は α 同値ではなく、本体の比較は x,y; a,b⊢x=αbx, y;\, a, b \vdash x \alphaeq b に到達して、idx\mathrm{idx} は xx に 11、bb に 00 を与える。逆に λ(x:A). λ(x:A). x\tLam{x}{A}{\tLam{x}{A}{x}} と λ(a:A). λ(b:A). b\tLam{a}{A}{\tLam{b}{A}{b}} は α 同値であり、(x,x)(x, x) における xx の最後の出現のインデックスは 00 で、(a,b)(a, b) における bb と同じである。

図 11.1 の規則は定義 11.3 とは別の定義なので、両者が一致することを確かめなければならない。

定理 11.4(アルゴリズム的 α 同値の正しさ). すべての項 ss と tt について、定義 11.3 の意味で s=αts \alphaeq t であることと、⋅ ;⋅⊢s=αt\cdot\,;\cdot \vdash s \alphaeq t が図 11.1 の規則で導出できることとは同値である。

証明の要点は、どちらの定義も「ss と tt が同じ de Bruijn 表現を持つ」と言い換えられることである。アルゴリズムの規則は de Bruijn インデックスと同じ方法で位置を比較しているので、この言い換えはほぼ直接に分かる。一方、定義 11.3 については、束縛変数を改名しても de Bruijn 表現が変わらないことを示す必要がある。完全な証明は 付録 B にある。

演習

(λ(y:A). λ(x:B). x  y)[x↦y]\subst{(\tLam{y}{A}{\tLam{x}{B}{x\;y}})}{x}{y} を一歩ずつ計算せよ。

解答

外側の束縛子は yy で、これは置き換え項 yy に自由に現れるので、捕獲の場合にあたる。yy を新鮮な zz に改名して本体 λ(x:B). x  z\tLam{x}{B}{x\;z} を得てから代入する。内側の束縛子は置き換え対象の変数 xx なので、内側の本体には手を付けない。結果は λ(z:A). λ(x:B). x  z\tLam{z}{A}{\tLam{x}{B}{x\;z}} である。内側の xx はもともと自由な出現ではなかったので、実際には何も置き換わっておらず、唯一の効果は、捕獲回避が強いた改名である。

λ(x:A). λ(y:A). y  x\tLam{x}{A}{\tLam{y}{A}{y\;x}} と λ(y:A). λ(x:A). x  y\tLam{y}{A}{\tLam{x}{A}{x\;y}} が α 同値かどうかを、図 11.1 を実行して判定せよ。

解答

同値である。α\alpha-Lam を二回適用すると、束縛子のリストは x,yx, y と y,xy, x、本体は y  xy\;x と x  yx\;y になる。α\alpha-App で yy と xx を比べるとインデックスは 00 と 00、続いて xx と yy を比べると 11 と 11 になる。名前は入れ替わっていても、位置が一致するので同値である。

覚えておくこと

  • 代入は自由な出現だけを置き換え、置き換え項が束縛子の名前を含むとき(捕獲)は、束縛子を改名しなければならない。
  • 同じ捕獲の問題は文脈でも起こり、束縛子が文脈の項目をシャドーイングすることを許すカーネルは False を証明できてしまうが、Freshen がこれを防ぐ。
  • α 同値は、束縛変数をその束縛子の位置で、自由変数を名前で比較する。図 11.1 がそのアルゴリズムであり、定理 11.4 はそれが定義と一致することを述べる。
  • 三つとも src/substitution.rs にあり、subst、fv、alpha である。