第 7 章では自然数の再帰子を手作業で組み立てたが、Lean のユーザーはそのようなことをしない。inductive 宣言を書けば、カーネルがそれを検査し、型構成子、構成子、再帰子、そしてその計算規則を生成する。本章ではこの手続きを三段階で組み立て、まず構成子を増やし、次にパラメータ、最後に添字を扱い、そのうえで一般的な形で述べる。カーネルのこの部分のコードはおよそ五百行である。
構成子を増やす: 木
N a t \Nat Nat の構成子は二つで、再帰的引数を持つのはそのうち一つだけである。構成子が三つある型を考えよう。
inductive Tree where
| leaf : Tree
| node : Tree -> Tree
| branch : Tree -> Tree -> Tree
たとえば b r a n c h ( n o d e l e a f ) l e a f \mathsf{branch}\;(\mathsf{node}\;\mathsf{leaf})\;\mathsf{leaf} branch ( node leaf ) leaf は、四つの構成子から組み立てられた木である。M : T r e e → S o r t v M : \mathsf{Tree} \to \Sort{v} M : Tree → Sort v として、すべての木 t t t について M t M\;t M t を組み立てたい。第 7 章の手順に従えば、構成子ごとにケースが一つ必要で、型 T r e e \mathsf{Tree} Tree の引数一つにつき帰納法の仮定が一つ加わる。
m l e a f : M l e a f m n o d e : Π ( t : T r e e ) . M t → M ( n o d e t ) m b r a n c h : Π ( l : T r e e ) . Π ( r : T r e e ) . M l → M r → M ( b r a n c h l r ) \begin{array}{lcl}
m_{\mathsf{leaf}} &:& M\;\mathsf{leaf} \\[2pt]
m_{\mathsf{node}} &:& \tPi{t}{\mathsf{Tree}}{M\;t \to M\;(\mathsf{node}\;t)} \\[2pt]
m_{\mathsf{branch}} &:& \tPi{l}{\mathsf{Tree}}{\tPi{r}{\mathsf{Tree}}{M\;l \to M\;r \to M\;(\mathsf{branch}\;l\;r)}}
\end{array} m leaf m node m branch : : : M leaf Π ( t : Tree ) . M t → M ( node t ) Π ( l : Tree ) . Π ( r : Tree ) . M l → M r → M ( branch l r )
再帰子は motive、三つのケース、そして木を受け取る。
T r e e . r e c v : Π ( M : T r e e → S o r t v ) . M l e a f → ( ⋯ n o d e ) → ( ⋯ b r a n c h ) → Π ( t : T r e e ) . M t , \mathsf{Tree.rec}_v : \tPi{M}{\mathsf{Tree} \to \Sort{v}}{\;
M\;\mathsf{leaf} \to (\cdots_{\mathsf{node}}) \to (\cdots_{\mathsf{branch}}) \to \tPi{t}{\mathsf{Tree}}{M\;t}}, Tree.rec v : Π ( M : Tree → Sort v ) . M leaf → ( ⋯ node ) → ( ⋯ branch ) → Π ( t : Tree ) . M t ,
ここで点々は m n o d e m_{\mathsf{node}} m node と m b r a n c h m_{\mathsf{branch}} m branch の型を表す。N a t . r e c v \NatRec{v} Nat.rec v と比べて変わったのは、ケースの数と、ケースごとの仮定の数だけである。部分適用 T r e e . r e c v M m l e a f m n o d e m b r a n c h \mathsf{Tree.rec}_v\;M\;m_{\mathsf{leaf}}\;m_{\mathsf{node}}\;m_{\mathsf{branch}} Tree.rec v M m leaf m node m branch を R R R と書くと、ι 規則は次のようになる。
R l e a f → ι m l e a f R ( n o d e t ) → ι m n o d e t ( R t ) R ( b r a n c h l r ) → ι m b r a n c h l r ( R l ) ( R r ) . \begin{array}{rcl}
R\;\mathsf{leaf} &\iotared& m_{\mathsf{leaf}} \\[2pt]
R\;(\mathsf{node}\;t) &\iotared& m_{\mathsf{node}}\;t\;(R\;t) \\[2pt]
R\;(\mathsf{branch}\;l\;r) &\iotared& m_{\mathsf{branch}}\;l\;r\;(R\;l)\;(R\;r).
\end{array} R leaf R ( node t ) R ( branch l r ) → ι → ι → ι m leaf m node t ( R t ) m branch l r ( R l ) ( R r ) .
各規則は、大前提の頭部にある構成子に対応するケースを選び、各仮定を対応する部分木への再帰呼び出しで埋める。たとえば M = λ ( _ : T r e e ) . N a t M = \tLam{\_}{\mathsf{Tree}}{\Nat} M = λ ( _ : Tree ) . Nat とし、ケースを
m l e a f = 1 , m n o d e = λ ( t : T r e e ) . λ ( i : N a t ) . 1 + i , m b r a n c h = λ ( l : T r e e ) . λ ( r : T r e e ) . λ ( i : N a t ) . λ ( j : N a t ) . 1 + i + j , m_{\mathsf{leaf}} = 1, \qquad
m_{\mathsf{node}} = \tLam{t}{\mathsf{Tree}}{\tLam{i}{\Nat}{1 + i}}, \qquad
m_{\mathsf{branch}} = \tLam{l}{\mathsf{Tree}}{\tLam{r}{\mathsf{Tree}}{\tLam{i}{\Nat}{\tLam{j}{\Nat}{1 + i + j}}}}, m leaf = 1 , m node = λ ( t : Tree ) . λ ( i : Nat ) . 1 + i , m branch = λ ( l : Tree ) . λ ( r : Tree ) . λ ( i : Nat ) . λ ( j : Nat ) . 1 + i + j ,
とすると、R R R は木の構成子の数を数える。
R ( b r a n c h ( n o d e l e a f ) l e a f ) → 1 + R ( n o d e l e a f ) + R l e a f → 1 + ( 1 + R l e a f ) + 1 → 1 + ( 1 + 1 ) + 1 = 4. R\;(\mathsf{branch}\;(\mathsf{node}\;\mathsf{leaf})\;\mathsf{leaf})
\to 1 + R\;(\mathsf{node}\;\mathsf{leaf}) + R\;\mathsf{leaf}
\to 1 + (1 + R\;\mathsf{leaf}) + 1
\to 1 + (1 + 1) + 1 = 4. R ( branch ( node leaf ) leaf ) → 1 + R ( node leaf ) + R leaf → 1 + ( 1 + R leaf ) + 1 → 1 + ( 1 + 1 ) + 1 = 4.
M M M を述語にすれば(v = 0 v = 0 v = 0 )、同じ再帰子が木に関する構造的帰納法になる。一般に、構成子が n n n 個の型には n n n 個の小前提 m 1 , … , m n m_1, \ldots, m_n m 1 , … , m n を持つ再帰子が付く。カーネルにその型に固有のコードは要らず、構成子ごとに、どのケースを選び、どの引数で再帰するかを知っていればよい。
カーネルから見たこの木を示す。各ボックスは構成子の適用であり、再帰的フィールドはその下にぶら下がる。各ボックスの最下行はそれを処理する再帰子のケースで、( R l ) (R\;l) ( R l ) と ( R r ) (R\;r) ( R r ) は再帰呼び出しで計算される帰納法の仮定である。別の木を入力すると、そのケースが表示される。
inductive Tree : Sort 1 where
| leaf
| node (t : Tree)
| branch (l : Tree) (r : Tree)
inductive Tree : Sort 1 where
| leaf
| node (t : Tree)
| branch (l : Tree) (r : Tree)
def add : Nat -> Nat -> Nat := fun (m : Nat) => fun (n : Nat) => Nat.rec1 (fun (_n : Nat) => Nat) m (fun (k : Nat) => fun (r : Nat) => Nat.succ r) n
def one : Nat := Nat.succ Nat.zero
def four : Nat := Nat.succ (Nat.succ (Nat.succ one))
# branch の小前提は l と r を受け取り、続いて二つの帰納法の仮定を受け取る。
def count : Tree -> Nat := fun (t : Tree) => Tree.rec1 (fun (_t : Tree) => Nat) one (fun (t : Tree) => fun (i : Nat) => Nat.succ i) (fun (l : Tree) => fun (r : Tree) => fun (i : Nat) => fun (j : Nat) => Nat.succ (add i j)) t
goal Eq Nat (count (Tree.branch (Tree.node Tree.leaf) Tree.leaf)) four
rfl
qed
パラメータ: リスト
数のリストは、空であるか、数のリストの前に数を一つ置いたものである。そこで構成子 n i l \mathsf{nil} nil と c o n s : N a t → L i s t N a t → L i s t N a t \mathsf{cons} : \Nat \to \mathsf{ListNat} \to \mathsf{ListNat} cons : Nat → ListNat → ListNat を持つ型 L i s t N a t \mathsf{ListNat} ListNat を宣言してもよい。しかしそれでは、木のリストには同じ構成子と別の再帰子を持つ別の宣言 L i s t T r e e \mathsf{ListTree} ListTree が必要になり、要素の型ごとに同じことを繰り返すはめになる。要素の型を、型そのものの引数にしたほうがよい。
inductive List (A : Sort 1) where
| nil : List A
| cons : A -> List A -> List A
これで L i s t N a t \mathsf{List}\;\Nat List Nat と L i s t T r e e \mathsf{List}\;\mathsf{Tree} List Tree は一つの宣言の二つのインスタンスになる。この宣言は次を追加する。
L i s t : S o r t 1 → S o r t 1 , n i l : Π ( A : S o r t 1 ) . L i s t A , c o n s : Π ( A : S o r t 1 ) . A → L i s t A → L i s t A . \mathsf{List} : \Sort{1} \to \Sort{1}, \qquad
\mathsf{nil} : \tPi{A}{\Sort{1}}{\mathsf{List}\;A}, \qquad
\mathsf{cons} : \tPi{A}{\Sort{1}}{A \to \mathsf{List}\;A \to \mathsf{List}\;A}. List : Sort 1 → Sort 1 , nil : Π ( A : Sort 1 ) . List A , cons : Π ( A : Sort 1 ) . A → List A → List A .
構成子は A A A を明示的な第一引数として受け取り、たとえば一要素のリスト [ 5 ] [5] [ 5 ] は c o n s N a t 5 ( n i l N a t ) \mathsf{cons}\;\Nat\;5\;(\mathsf{nil}\;\Nat) cons Nat 5 ( nil Nat ) である。一つのリストの中で A A A は決して変わらず、c o n s A a a s \mathsf{cons}\;A\;a\;\mathit{as} cons A a as の尾部もやはり L i s t A \mathsf{List}\;A List A である。すべての構成子とすべての再帰的引数で同じままである型の引数をパラメータ (parameter)と呼ぶ。決して変わらないので、再帰子は motive より前に一度だけ受け取り、固定されたものとして扱える。
L i s t . r e c v : Π ( A : S o r t 1 ) . Π ( M : L i s t A → S o r t v ) . M ( n i l A ) → ( Π ( a : A ) . Π ( a s : L i s t A ) . M a s → M ( c o n s A a a s ) ) → Π ( x : L i s t A ) . M x L i s t . r e c v A M m 1 m 2 ( n i l A ′ ) → ι m 1 L i s t . r e c v A M m 1 m 2 ( c o n s A ′ a a s ) → ι m 2 a a s ( L i s t . r e c v A M m 1 m 2 a s ) \begin{array}{l}
\mathsf{List.rec}_v : \tPi{A}{\Sort{1}}{\tPi{M}{\mathsf{List}\;A \to \Sort{v}}{\;M\;(\mathsf{nil}\;A) \to {}}} \\[2pt]
\qquad (\tPi{a}{A}{\tPi{\mathit{as}}{\mathsf{List}\;A}{M\;\mathit{as} \to M\;(\mathsf{cons}\;A\;a\;\mathit{as})}}) \to \tPi{x}{\mathsf{List}\;A}{M\;x} \\[8pt]
\mathsf{List.rec}_v\;A\;M\;m_1\;m_2\;(\mathsf{nil}\;A') \;\iotared\; m_1 \\[2pt]
\mathsf{List.rec}_v\;A\;M\;m_1\;m_2\;(\mathsf{cons}\;A'\;a\;\mathit{as}) \;\iotared\; m_2\;a\;\mathit{as}\;(\mathsf{List.rec}_v\;A\;M\;m_1\;m_2\;\mathit{as})
\end{array} List.rec v : Π ( A : Sort 1 ) . Π ( M : List A → Sort v ) . M ( nil A ) → ( Π ( a : A ) . Π ( as : List A ) . M as → M ( cons A a as )) → Π ( x : List A ) . M x List.rec v A M m 1 m 2 ( nil A ′ ) → ι m 1 List.rec v A M m 1 m 2 ( cons A ′ a as ) → ι m 2 a as ( List.rec v A M m 1 m 2 as )
先頭の A A A を除けば、これも第 7 章の手順そのものである。c o n s \mathsf{cons} cons の引数は二つで、再帰的なのは a s \mathit{as} as だけなので、そのケースには帰納法の仮定が一つ付く。型の付いた項では構成子に格納されたパラメータ A ′ A' A ′ は A A A と変換可能なので、規則はこれを無視する。たとえばリストの長さは次のように書ける。
l e n g t h : = λ ( A : S o r t 1 ) . λ ( x : L i s t A ) . L i s t . r e c 1 A ( λ ( _ : L i s t A ) . N a t ) z e r o ( λ ( a : A ) . λ ( a s : L i s t A ) . λ ( r : N a t ) . s u c c r ) x . \mathit{length} \mathrel{:=} \tLam{A}{\Sort{1}}{\tLam{x}{\mathsf{List}\;A}{
\mathsf{List.rec}_1\;A\;(\tLam{\_}{\mathsf{List}\;A}{\Nat})\;\zero\;(\tLam{a}{A}{\tLam{\mathit{as}}{\mathsf{List}\;A}{\tLam{r}{\Nat}{\suc\;r}}})\;x}}. length := λ ( A : Sort 1 ) . λ ( x : List A ) . List.rec 1 A ( λ ( _ : List A ) . Nat ) zero ( λ ( a : A ) . λ ( as : List A ) . λ ( r : Nat ) . succ r ) x .
リストの値はすべての構成子にパラメータ A A A を持ち運び、下の木では各ボックスの横にそれが表示される。パラメータは繰り返され、フィールドは異なる。
inductive List (A : Sort 1) : Sort 1 where
| nil
| cons (head : A) (tail : List A)
inductive List (A : Sort 1) : Sort 1 where
| nil
| cons (head : A) (tail : List A)
def length : forall (A : Sort 1), List A -> Nat := fun (A : Sort 1) => fun (xs : List A) => List.rec1 A (fun (_xs : List A) => Nat) Nat.zero (fun (head : A) => fun (tail : List A) => fun (r : Nat) => Nat.succ r) xs
# length [0, 0] = 2
goal Eq Nat (length Nat (List.cons Nat Nat.zero (List.cons Nat Nat.zero (List.nil Nat)))) (Nat.succ (Nat.succ Nat.zero))
rfl
qed
添字: ベクトル
L i s t N a t \mathsf{List}\;\Nat List Nat の長さは任意である。長さを型の一部にして、V e c N a t 0 \mathsf{Vec}\;\Nat\;0 Vec Nat 0 と V e c N a t 2 \mathsf{Vec}\;\Nat\;2 Vec Nat 2 を別の型にしたいとしたらどうなるか。
inductive Vec (A : Sort 1) : Nat -> Sort 1 where
| vnil : Vec A 0
| vcons : (n : Nat) -> A -> Vec A n -> Vec A (succ n)
要素の型 A A A は依然としてパラメータだが、長さはそうではなく、v n i l \mathsf{vnil} vnil の長さは 0 0 0 で、v c o n s \mathsf{vcons} vcons は長さ n n n のベクトルを受け取って長さ s u c c n \suc\;n succ n のベクトルを返す。構成子の間で、また構成子とその再帰的引数の間で異なりうる型の引数を添字 (index)と呼ぶ。構成子は次のとおりである。
v n i l : Π ( A : S o r t 1 ) . V e c A z e r o , v c o n s : Π ( A : S o r t 1 ) . Π ( n : N a t ) . A → V e c A n → V e c A ( s u c c n ) . \mathsf{vnil} : \tPi{A}{\Sort{1}}{\mathsf{Vec}\;A\;\zero}, \qquad
\mathsf{vcons} : \tPi{A}{\Sort{1}}{\tPi{n}{\Nat}{A \to \mathsf{Vec}\;A\;n \to \mathsf{Vec}\;A\;(\suc\;n)}}. vnil : Π ( A : Sort 1 ) . Vec A zero , vcons : Π ( A : Sort 1 ) . Π ( n : Nat ) . A → Vec A n → Vec A ( succ n ) .
添字は変化するので、再帰子はそれをあらかじめ固定できず、代わりに motive が引数として受け取る。M : Π ( n : N a t ) . V e c A n → S o r t v M : \tPi{n}{\Nat}{\mathsf{Vec}\;A\;n \to \Sort{v}} M : Π ( n : Nat ) . Vec A n → Sort v であり、v c o n s \mathsf{vcons} vcons のケースは
Π ( n : N a t ) . Π ( a : A ) . Π ( x s : V e c A n ) . M n x s → M ( s u c c n ) ( v c o n s A n a x s ) . \tPi{n}{\Nat}{\tPi{a}{A}{\tPi{\mathit{xs}}{\mathsf{Vec}\;A\;n}{M\;n\;\mathit{xs} \to M\;(\suc\;n)\;(\mathsf{vcons}\;A\;n\;a\;\mathit{xs})}}}. Π ( n : Nat ) . Π ( a : A ) . Π ( xs : Vec A n ) . M n xs → M ( succ n ) ( vcons A n a xs ) .
となる。帰納法の仮定は長さ n n n のベクトルについてのもので、再帰的引数の型 V e c A n \mathsf{Vec}\;A\;n Vec A n から読み取る一方、結論は長さ s u c c n \suc\;n succ n のベクトルについてのもので、構成子の結果型から読み取る。スクリプト examples/generic-vec.leanmini(付録 E )はこの型を宣言し、それを使う小さな項を検査する。
一般的な手順
本節では、パラメータと添字を持つ単一の帰納型に対する手続きを述べるが、Lean が対応している相互帰納型と入れ子帰納型は範囲外とする。
この手続きは、T-Const が未解決のまま残した問いにも答える。本体のない定数は仮定であり、T-Const は z e r o : N a t \zero : \Nat zero : Nat を受け入れるのとまったく同じように c : Π ( P : S o r t 0 ) . P c : \tPi{P}{\Sort{0}}{P} c : Π ( P : Sort 0 ) . P も受け入れ、それがあると体系は非整合になる。以下で生成する定数は恣意的な仮定ではなく、固定されたスキーマのインスタンス、すなわち Calculus of Inductive Constructions の帰納型であり、その整合性は知られている [Werner 1994]。
テレスコープ。 束縛の列 ( x 1 : A 1 ) ⋯ ( x k : A k ) (x_1 : A_1) \cdots (x_k : A_k) ( x 1 : A 1 ) ⋯ ( x k : A k ) で、各 A i A_i A i が x 1 , … , x i − 1 x_1, \ldots, x_{i-1} x 1 , … , x i − 1 に言及してよいものを ( x ⃗ : A ⃗ ) (\vec{x} : \vec{A}) ( x : A ) と書く。Π ( x 1 : A 1 ) . ⋯ Π ( x k : A k ) . B \tPi{x_1}{A_1}{\cdots\,\tPi{x_k}{A_k}{B}} Π ( x 1 : A 1 ) . ⋯ Π ( x k : A k ) . B を Π ( x ⃗ : A ⃗ ) . B \tPi{\vec{x}}{\vec{A}}{B} Π ( x : A ) . B と書き、f x 1 ⋯ x k f\;x_1 \cdots x_k f x 1 ⋯ x k を f x ⃗ f\;\vec{x} f x と書く。x ⃗ \vec{x} x の長さは ∣ x ⃗ ∣ |\vec{x}| ∣ x ∣ である。コードではテレスコープは Context であり、src/term.rs の telescope(binders, tail) が入れ子の Π \Pi Π を組み立てる。
宣言。 宣言は型構成子 I I I と構成子 c 1 , … , c n c_1, \ldots, c_n c 1 , … , c n を導入する。
I : Π ( p ⃗ : P ⃗ ) . Π ( ı ⃗ : J ⃗ ) . S o r t u c j : Π ( p ⃗ : P ⃗ ) . Π ( a ⃗ : A ⃗ j ) . I p ⃗ t ⃗ j ( j = 1 , … , n ) \begin{array}{rcll}
I &:& \tPi{\vec{p}}{\vec{P}}{\tPi{\vec{\imath}}{\vec{J}}{\Sort{u}}} & \\[2pt]
c_j &:& \tPi{\vec{p}}{\vec{P}}{\tPi{\vec{a}}{\vec{A}_j}{I\;\vec{p}\;\vec{t}_j}} & (j = 1, \ldots, n)
\end{array} I c j : : Π ( p : P ) . Π ( : J ) . Sort u Π ( p : P ) . Π ( a : A j ) . I p t j ( j = 1 , … , n )
パラメータ p ⃗ \vec{p} p はすべての構成子に共通で、すべての結果型に変わらぬ形で現れる。添字 ı ⃗ \vec{\imath} は構成子ごとに異なってよく、添字項 t ⃗ j \vec{t}_j t j は p ⃗ \vec{p} p と、構成子自身の引数 a ⃗ \vec{a} a に言及してよい。この引数をフィールド (field)と呼ぶ。これまでの例について、構成子の型からパラメータに関する先頭の Π \Pi Π を省いて示すと次のようになる。
型構成子
パラメータ
添字
構成子
N a t : S o r t 1 \Nat : \Sort{1} Nat : Sort 1
なし
なし
z e r o : N a t \zero : \Nat zero : Nat , s u c c : N a t → N a t \;\suc : \Nat \to \Nat succ : Nat → Nat
T r e e : S o r t 1 \mathsf{Tree} : \Sort{1} Tree : Sort 1
なし
なし
l e a f \mathsf{leaf} leaf , n o d e \mathsf{node} node , b r a n c h \mathsf{branch} branch
L i s t : S o r t 1 → S o r t 1 \mathsf{List} : \Sort{1} \to \Sort{1} List : Sort 1 → Sort 1
( A : S o r t 1 ) (A : \Sort{1}) ( A : Sort 1 )
なし
n i l : L i s t A \mathsf{nil} : \mathsf{List}\;A nil : List A , c o n s : A → L i s t A → L i s t A \;\mathsf{cons} : A \to \mathsf{List}\;A \to \mathsf{List}\;A cons : A → List A → List A
V e c : S o r t 1 → N a t → S o r t 1 \mathsf{Vec} : \Sort{1} \to \Nat \to \Sort{1} Vec : Sort 1 → Nat → Sort 1
( A : S o r t 1 ) (A : \Sort{1}) ( A : Sort 1 )
( n : N a t ) (n : \Nat) ( n : Nat )
v n i l : V e c A z e r o \mathsf{vnil} : \mathsf{Vec}\;A\;\zero vnil : Vec A zero , v c o n s : Π ( n : N a t ) . A → V e c A n → V e c A ( s u c c n ) \;\mathsf{vcons} : \tPi{n}{\Nat}{A \to \mathsf{Vec}\;A\;n \to \mathsf{Vec}\;A\;(\suc\;n)} vcons : Π ( n : Nat ) . A → Vec A n → Vec A ( succ n )
受理。 カーネルは、次の四つの条件がすべて成り立つときに限り宣言を受理する。
I I I の型が、現在のシグネチャにおいて型である。
I I I をシグネチャに加えたうえで、各構成子の型が型であり、末尾が I p ⃗ t ⃗ j I\;\vec{p}\;\vec{t}_j I p t j である。ここで p ⃗ \vec{p} p はちょうどパラメータをその順に並べたもので、I I I は t ⃗ j \vec{t}_j t j に現れない。
正格な正値性。 各フィールドの型は、I I I に言及しないか、Π ( y ⃗ : B ⃗ ) . I p ⃗ s ⃗ \tPi{\vec{y}}{\vec{B}}{I\;\vec{p}\;\vec{s}} Π ( y : B ) . I p s の形で I I I が B ⃗ \vec{B} B にも s ⃗ \vec{s} s にも現れないかのいずれかである。後者の形のフィールドを再帰的 (recursive)と呼ぶ。
宇宙。 u ≥ 1 u \ge 1 u ≥ 1 なら、各フィールドの型はある S o r t w \Sort{w} Sort w (w ≤ u w \le u w ≤ u )に属する。u = 0 u = 0 u = 0 なら条件はない。
条件 2 により、再帰子はパラメータを一様に扱える。条件 3 は次のような宣言を排除する。
inductive Bad where
| mk : (Bad -> False) -> Bad
このフィールドは矢印の左側で Bad に言及しており、Bad に対する場合分けが許されると、f ( m k g ) = g ( m k g ) f\;(\mathsf{mk}\;g) = g\;(\mathsf{mk}\;g) f ( mk g ) = g ( mk g ) によって f : B a d → F a l s e f : \mathsf{Bad} \to \mathsf{False} f : Bad → False を定義できてしまう。すると f ( m k f ) f\;(\mathsf{mk}\;f) f ( mk f ) は型 F a l s e \mathsf{False} False の閉じた項であり、自分自身に簡約されるので正規形を持たない。宣言中の型 T r e e \mathsf{Tree} Tree について、フィールドの型とその扱いをいくつか示す。
フィールドの型
扱い
理由
N a t \Nat Nat
受理、非再帰的
T r e e \mathsf{Tree} Tree に言及しない
T r e e \mathsf{Tree} Tree
受理、再帰的
帰納法の仮定は M a k M\;a_k M a k
N a t → T r e e \Nat \to \mathsf{Tree} Nat → Tree
受理、再帰的
木を生み出す。仮定は Π ( n : N a t ) . M ( a k n ) \tPi{n}{\Nat}{M\;(a_k\;n)} Π ( n : Nat ) . M ( a k n )
T r e e → N a t \mathsf{Tree} \to \Nat Tree → Nat
拒否
T r e e \mathsf{Tree} Tree が矢印の左側にある
L i s t T r e e \mathsf{List}\;\mathsf{Tree} List Tree
ここでは拒否
入れ子の出現。Lean は受理するが範囲外
型 N a t → T r e e \Nat \to \mathsf{Tree} Nat → Tree のフィールドは、数ごとに一つずつ、無限個の子を持つ木だが、再帰子はそれでも各子 a k n a_k\;n a k n へ再帰できる。型 T r e e → N a t \mathsf{Tree} \to \Nat Tree → Nat のフィールドは木を含むのではなく木を消費 し、これが Bad をパラドックスにする形である。
条件 4 は、型がより大きな宇宙を含むことを防ぐ。これがなければ、構成子 m k : S o r t 1 → T \mathsf{mk} : \Sort{1} \to T mk : Sort 1 → T を持つ宣言 T : S o r t 1 T : \Sort{1} T : Sort 1 によって、S o r t 1 \Sort{1} Sort 1 全体の複製が S o r t 1 \Sort{1} Sort 1 の内側に置かれ、これは第 13 章 が非整合だと示す自己包含である。u = 0 u = 0 u = 0 では、S o r t 0 \Sort{0} Sort 0 が非述語的なので条件は要らない。
再帰子。 受理された宣言について、カーネルは許容される各レベル v v v (後述)に対して再帰子 I . r e c v I.\mathsf{rec}_v I . rec v を生成する。
I . r e c v : Π ( p ⃗ : P ⃗ ) . Π ( M : ( Π ( ı ⃗ : J ⃗ ) . I p ⃗ ı ⃗ → S o r t v ) ) . Π ( m 1 : C 1 ) . ⋯ Π ( m n : C n ) . Π ( ı ⃗ : J ⃗ ) . Π ( x : I p ⃗ ı ⃗ ) . M ı ⃗ x . I.\mathsf{rec}_v :
\tPi{\vec{p}}{\vec{P}}{\;
\tPi{M}{(\tPi{\vec{\imath}}{\vec{J}}{I\;\vec{p}\;\vec{\imath} \to \Sort{v}})}{\;
\tPi{m_1}{C_1}{\cdots\,\tPi{m_n}{C_n}{\;
\tPi{\vec{\imath}}{\vec{J}}{\tPi{x}{I\;\vec{p}\;\vec{\imath}}{M\;\vec{\imath}\;x}}}}}}. I . rec v : Π ( p : P ) . Π ( M : ( Π ( : J ) . I p → Sort v )) . Π ( m 1 : C 1 ) . ⋯ Π ( m n : C n ) . Π ( : J ) . Π ( x : I p ) . M x .
パラメータが先頭に来て固定されるのはリストと同じで、motive が添字と要素を受け取るのはベクトルと同じである。小前提 C j C_j C j は構成子 c j c_j c j のケースを求める。そのフィールドと、再帰的フィールドごとの帰納法の仮定が与えられたとき、c j c_j c j をそれらのフィールドに適用したものについて M M M を組み立てよ、というものである。
C j = Π ( a ⃗ : A ⃗ j ) . Π ( h ⃗ : H ⃗ j ) . M t ⃗ j ( c j p ⃗ a ⃗ ) , H j , k = Π ( y ⃗ : B ⃗ ) . M s ⃗ ( a k y ⃗ ) for each recursive field a k : Π ( y ⃗ : B ⃗ ) . I p ⃗ s ⃗ . \begin{array}{l}
C_j = \tPi{\vec{a}}{\vec{A}_j}{\;\tPi{\vec{h}}{\vec{H}_j}{\;M\;\vec{t}_j\;(c_j\;\vec{p}\;\vec{a})}}, \\[4pt]
H_{j,k} = \tPi{\vec{y}}{\vec{B}}{M\;\vec{s}\;(a_k\;\vec{y})}
\quad\text{for each recursive field } a_k : \tPi{\vec{y}}{\vec{B}}{I\;\vec{p}\;\vec{s}}.
\end{array} C j = Π ( a : A j ) . Π ( h : H j ) . M t j ( c j p a ) , H j , k = Π ( y : B ) . M s ( a k y ) for each recursive field a k : Π ( y : B ) . I p s .
最後の引数は添字と要素 x x x 自身、すなわち大前提である。
規則。 構成子ごとに、カーネルは ι 規則を一つ生成し、規則は小前提を、フィールドと、再帰呼び出しで計算した帰納法の仮定に適用する。
I . r e c v p ⃗ M m ⃗ ı ⃗ ( c j p ⃗ ′ a ⃗ ) → ι m j a ⃗ h ⃗ , h k = λ ( y ⃗ : B ⃗ ) . I . r e c v p ⃗ M m ⃗ s ⃗ ( a k y ⃗ ) . I.\mathsf{rec}_v\;\vec{p}\;M\;\vec{m}\;\vec{\imath}\;(c_j\;\vec{p}\,'\;\vec{a}) \;\iotared\; m_j\;\vec{a}\;\vec{h},
\qquad
h_k = \tLam{\vec{y}}{\vec{B}}{I.\mathsf{rec}_v\;\vec{p}\;M\;\vec{m}\;\vec{s}\;(a_k\;\vec{y})}. I . rec v p M m ( c j p ′ a ) → ι m j a h , h k = λ ( y : B ) . I . rec v p M m s ( a k y ) .
大前提の位置は(ゼロから数えて)∣ p ⃗ ∣ + 1 + n + ∣ ı ⃗ ∣ |\vec{p}| + 1 + n + |\vec{\imath}| ∣ p ∣ + 1 + n + ∣ ∣ であり、構成子は ∣ p ⃗ ∣ |\vec{p}| ∣ p ∣ 個のパラメータと ∣ a ⃗ ∣ |\vec{a}| ∣ a ∣ 個のフィールドを持つ。型の付いた簡約基では p ⃗ ′ \vec{p}\,' p ′ は p ⃗ \vec{p} p と変換可能なので、リストの規則が A ′ A' A ′ を無視したのと同様に、規則はこれを無視する。N a t \Nat Nat 、T r e e \mathsf{Tree} Tree 、L i s t \mathsf{List} List 、V e c \mathsf{Vec} Vec について、この手続きは本章と前章の再帰子と規則をそのまま生成する。再帰子の型は、束縛変数の名前と暗黙の引数を除いて、Lean 4 が生成するものと一致する。
命題からの消去。 u ≥ 1 u \ge 1 u ≥ 1 なら、再帰子はすべてのレベル v v v について生成されるが、u = 0 u = 0 u = 0 、つまり I I I が命題なら、再帰子は v = 0 v = 0 v = 0 についてのみ生成される。ただし I I I が部分単集合 (subsingleton)なら話は別である。部分単集合とは、構成子が高々一つで、各フィールドが証明である(型が S o r t 0 \Sort{0} Sort 0 に属する)か添字項 t ⃗ j \vec{t}_j t j の中に現れるかのいずれかである型をいう。この制限は、命題のどの 証明が与えられたかにデータが依存することを防ぐ。たとえば P ∨ Q P \vee Q P ∨ Q には構成子が二つあるので、P ∨ Q P \vee Q P ∨ Q の証明から N a t \Nat Nat への関数は一方に 0 0 0 を、他方に 1 1 1 を返せてしまい、証明が計算上の情報を持つことになる。したがって Lean の Or.rec と Exists.rec は Prop へのみ消去し、And.rec と Eq.rec はすべての宇宙へ消去する。And の構成子は一つでそのフィールドはすべて証明であり、Eq(第 9 章 )の構成子は一つでフィールドを持たないからである。
こうして、シグネチャが成長してもカーネルの規則は固定されたままである。一つの汎用の手続きが各宣言をシグネチャの項目に変え、一つの簡約関数がそれらの規則を実行する(第 12 章 )。
コードでは
この手続きは src/inductive/ にあり、入力は InductiveSpec(src/declaration.rs)で、名前、パラメータと添字のテレスコープ、宇宙レベル u u u 、そして構成子ごとにそのフィールドと添字項 t ⃗ j \vec{t}_j t j を与える ConstructorSpec からなる。スクリプトのパーサが inductive ブロックからこれを組み立てる。
src/inductive/mod.rs の Kernel::add_inductive は四つの受理条件を順に実行し(check_inductive_type が条件 1、check_constructor_fields がフィールドが型であることを確かめて条件 4 を課し、check_constructor_result が条件 2)、すべて通れば型構成子と構成子を登録して InductiveInfo を記録する。失敗した場合、シグネチャは変更されない。
src/inductive/positivity.rs が条件 3 であり、check_recursive_field はフィールドの型の先頭の Π \Pi Π を剥がし、定義域が I I I に言及するものを拒否する。そして標的が、I I I をちょうどパラメータ変数に適用し、その後に I I I に言及しない添字項を続けた形であることを要求する。戻り値は y ⃗ : B ⃗ \vec{y} : \vec{B} y : B と s ⃗ \vec{s} s を記録した RecursiveFieldInfo で、再帰子の生成器に必要なのはこれだけである。
src/inductive/recursor.rs は上の式から再帰子の型を組み立てる。recursor_type がテレスコープ p ⃗ \vec{p} p 、motive、小前提、添字、大前提を組み合わせ、minor_premise_type が各 C j C_j C j を、induction_hypothesis_type が各 H j , k H_{j,k} H j , k を組み立てる。生成される束縛子の名前(__motive、__minor_j、__ih_j_k、__major)は下線二つで始まり、ユーザーの宣言ではこの形の名前を使えないので、フィールドと衝突することはない。
mod.rs の allows_large_elimination が部分単集合の判定を実装する。add_inductive は I.rec0 を常に登録し、大消去が許されるなら I.rec1 も登録する。より高いレベルは register_recursor が必要に応じて生成する。
ι 規則は項としては生成されず、whnf が、格納された InductiveInfo から直接それを適用する。詳細は第 12 章 で説明する。
生成器の中心部は、全文を読めるほど短いので、C j C_j C j の式と見比べてほしい。
/// C_j = Pi (as : As_j). Pi (hs : Hs_j). M ts_j (c_j ps as)
fn minor_premise_type(info: &InductiveInfo , j: usize, constructor: &ConstructorInfo ) -> Term {
// c_j ps as
let constructor_application = apps(
const_term(&constructor.name),
info.params.iter().chain(constructor.fields.iter()).map(|(name, _)| var(name)),
);
// (as : As_j) の後に (hs : Hs_j)
let mut minor_premise_binders = constructor.fields.clone();
for (k, recursive_field) in constructor.recursive_fields.iter().enumerate() {
minor_premise_binders.push((
format! ("{INDUCTION_HYPOTHESIS_PREFIX}{j}_{k}" ),
induction_hypothesis_type(constructor, recursive_field),
));
}
telescope(&minor_premise_binders, apply_motive(&constructor.result_indices, constructor_application))
}
カーネルは N a t \Nat Nat を特別扱いせず、src/kernel.rs の Kernel::standard は、ユーザーのスクリプトが生成するのと同じ InductiveSpec で add_inductive を呼んで N a t \Nat Nat と E q \mathsf{Eq} Eq を宣言する。つまり図 7.1 はハードコードされたものではなく生成された ものであり、src/inductive/tests.rs のテストは、生成された型が手書きのものと一致することを確かめる。
正値性の検査は健全性のための要件であって、便宜のためのものではない。Lean、Coq、Agda のいずれも、一般の場合(入れ子帰納型と相互帰納型、宇宙制約、他の帰納型に言及する添字)が複雑なため、カーネルのこの部分で長年にわたりバグを出してきた。本書の制限されたスキーマでは検査は八十行に収まるが、拡張するときに新機能のたびに問うべきことは一つである。その型を消費する関数から、その型の値を組み立てられないか。
演習
inductive Bool where | true | false に対する再帰子の型 B o o l . r e c v \mathsf{Bool.rec}_v Bool.rec v を書き、それを使って否定を定義せよ。
解答
B o o l . r e c v : Π ( M : B o o l → S o r t v ) . M t r u e → M f a l s e → Π ( b : B o o l ) . M b \mathsf{Bool.rec}_v : \tPi{M}{\mathsf{Bool} \to \Sort{v}}{M\;\mathsf{true} \to M\;\mathsf{false} \to \tPi{b}{\mathsf{Bool}}{M\;b}} Bool.rec v : Π ( M : Bool → Sort v ) . M true → M false → Π ( b : Bool ) . M b であり、どちらの構成子もフィールドを持たないので帰納法の仮定はない。否定は λ ( b : B o o l ) . B o o l . r e c 1 ( λ ( _ : B o o l ) . B o o l ) f a l s e t r u e b \tLam{b}{\mathsf{Bool}}{\mathsf{Bool.rec}_1\;(\tLam{\_}{\mathsf{Bool}}{\mathsf{Bool}})\;\mathsf{false}\;\mathsf{true}\;b} λ ( b : Bool ) . Bool.rec 1 ( λ ( _ : Bool ) . Bool ) false true b であり、再帰的フィールドを持たない型の再帰子は単なる場合分けである。
次のそれぞれは、四つの受理条件のどれによって拒否されるか。拒否されないものはあるか。(a) inductive T : Sort 1 where | mk (f : Sort 1)。(b) inductive T : Sort 1 where | mk (f : T -> T)。(c) inductive T : Sort 0 where | mk (f : Sort 1)。
解答
(a) 条件 4。フィールド S o r t 1 \Sort{1} Sort 1 の型は S o r t 2 \Sort{2} Sort 2 で、u = 1 u = 1 u = 1 を超える。(b) 条件 3。フィールドの型の中で T T T が矢印の左側に現れる。(c) 受理される。u = 0 u = 0 u = 0 では条件 4 は何も課さず、この型は型を一つ包み込んだ命題である。これは S o r t 0 \Sort{0} Sort 0 の非述語性の現れだが、T T T は S o r t 0 \Sort{0} Sort 0 へしか消去できない(フィールドが証明でも添字でもないので部分単集合ではない)ため無害である。
覚えておくこと
宣言は、パラメータと添字を持つ型構成子と、フィールドを持つ構成子を与え、パラメータは全体を通して固定される一方、添字は変化して motive の引数になる。
再帰子は構成子ごとに小前提を一つ持ち、各小前提は再帰的フィールドごとに帰納法の仮定を一つ持ち、ι 規則は構成子ごとに一つである。
受理には、整形式性、一様なパラメータ、正格な正値性、宇宙の上限が必要であり、再帰を停止させ、論理を整合的に保つのは正値性である。
命題は、部分単集合でない限り S o r t 0 \Sort{0} Sort 0 へしか消去できない。
カーネルはこれらすべてをデータから生成し、N a t \Nat Nat に固有のコードは含まれない。