Upload
kyoko-kadowaki
View
790
Download
2
Embed Size (px)
Citation preview
http://github.com/KDXU/InferAgda
Agdaによる型推論器の定式化
お茶の水女子大学大学院 門脇香子,浅井健一 [email protected] [email protected]
1
http://github.com/KDXU/InferAgda
研究概要• Agda を用いて停止性と正当性がそれぞれ保証された型推論器を pure functional に実装した
• unification の実装部分は McBride の手法を採用した
• 主に unification の部分と application の実装にフォーカスを当てながら解説します
• コード : http://github.com/KDXU/InferAgda
2
http://github.com/KDXU/InferAgda
Agdaについて
• 依存型を用いた,Haskell に似た構文をもつ定理証明支援系言語
• プログラミング言語と定理証明支援系言語の両面を持っている → 何かを実装しつつ証明をするのに適している
• マルティンレフの型理論に基づいている
3
http://github.com/KDXU/InferAgda
発表の流れ1. 研究概要
2. 研究背景
3. シンタックスと型定義
4. unification の実装
5. well-scoped term における実装
6. well-typed term における実装
7. まとめ
4
http://github.com/KDXU/InferAgda
研究背景• 依存型を使うと対象言語の型を項の定義に埋め込むことができる
• しかし,これまでの依存型による型チェックは,束縛変数の型があらかじめユーザによって入力されている→ 型推論は行わない!
• 型の unification を Agda で表現するのは難しかった
• McBride は停止性が明らかな形で型の unification が表現できることを示した→ Agda で型推論器を実装する土台が整った
5
http://github.com/KDXU/InferAgda
シンタックスと型定義• シンタックス自由変数の数がn個のwell-scoped term
• 依存型を用いた de Bruijn index
6
http://github.com/KDXU/InferAgda
依存型• 値に依存する型のこと
• 「Int 型を持つ項」なども表現できるようになる
• さまざまな面白い応用が知られている
7
List n 型 … 「長さ n のリスト」という意味を持つ型
http://github.com/KDXU/InferAgda
シンタックスと型定義
• 型定義
8
http://github.com/KDXU/InferAgda
unification• McBride の手法を Agda で実装したもの
• unification は書き換え可能なセルを使うと実装できる→ Agda だと書き換えは許されない & 停止性が保証されている必要がある
9
http://github.com/KDXU/InferAgda
unification• McBride の手法を Agda で実装したもの
• unification は書き換え可能なセルを使うと実装できる→ Agda だと書き換えは許されない & 停止性が保証されている必要がある
10
そこでMcBrideは「型変数が具体化されるたびに,型変数の数が一つ減る」という点に注目
http://github.com/KDXU/InferAgda
unification
• n 個の「具体化されていない型変数」を 0 から n-1までの数字からなる集合の型を表す Fin n 型で表す
• その n を減らすことで停止性が明らかな形の unification を実現
11
http://github.com/KDXU/InferAgda
unification
12
Fin 2Fin 3
=
http://github.com/KDXU/InferAgda
unification の実装の流れ
1. 変数を濃縮する関数 thick を実装 2. 変数の出現を確認する check を実装 3. 代入を表すデータ構造 AList を定義 4. mgu を定義
13
http://github.com/KDXU/InferAgda
thick 関数• 変数 y を x の位置で「濃縮」する関数 • 例 : x = 1 で濃縮 y = 1 だと濃縮できない
14
Fin 2 Fin 1
http://github.com/KDXU/InferAgda
thick 関数
15
http://github.com/KDXU/InferAgda
check 関数check x t : x 番の型変数が型 t の中に現れるかを thick を用いてチェックする
16
http://github.com/KDXU/InferAgda
AList 型• AList m n :「m 個の型変数を持つ型」を「n 個の型変数を持つ型」に変換するような代入の型 (m, n は m ≥ n であるような自然数)
17
http://github.com/KDXU/InferAgda
flexFlex,flexRigid
• flexFlex x y : 型変数 x と y を単一化する代入
18
http://github.com/KDXU/InferAgda
flexFlex,flexRigid
• flexRigid : 型変数 x と 型 t を単一化する代入
19
http://github.com/KDXU/InferAgda
mgu 関数
• 型 s と型 t を同じにするような代入を返す
• mgu は結果として (n, 代入) を返す
• 代入は m 個の型変数を n 個まで減らす
• amgu : accumulator を使って実装
20
http://github.com/KDXU/InferAgda
•型 s と t の構造に従って素直に unification を行う •m についての構造的帰納法を用いている •片方,または両方が型変数だった場合は flexRigid, flexFlex を使って unification を行っている
• (r for z) ◁ s は型 s の中の型変数全てについて (r for z) を施した型
21
http://github.com/KDXU/InferAgda
unification
22
• 以上で unify t1 t2 は型変数が m 個であるような型 t1 と t2 を unify する代入を返す関数として実装ができた
http://github.com/KDXU/InferAgda
well-scoped な型推論
. infer Γ s : 型環境 Γ と well-scoped な項 s をもらってき たら, s の型と必要な代入を Maybe モナドに包んで返す
23
σ Γ ⊢ s : τ
入力
出力
http://github.com/KDXU/InferAgda
well-scoped な型推論
24
count : Lam と App のノード数を数える関数
http://github.com/KDXU/InferAgda
variable
• 変数の場合は単に型環境の型を返す
25
http://github.com/KDXU/InferAgda
lambda• まず body 部分の型推論を行い,body 部分に型が付いた場合のみ型付けされ,「引数の型⇒ body の型」が全体の型となる
26
http://github.com/KDXU/InferAgda
application
27
1. App s1 s2 において,s1 の型推論によって返された代入 σ1 を Γ に加え,
2. 環境 σ1 Γ の元 での s2 の型推論が成功した場合にのみ,
3. (s2 の型⇒ β)が s1 と等しくなっているかどうかを unify 関数によって判定する
http://github.com/KDXU/InferAgda
application
28
http://github.com/KDXU/InferAgda
application
29
1. はじめに再帰呼び出しから infer {m} Γ s1 を行うと σ1 Γ ⊢ s1 : t1 なる σ1, t1 が得られる.
2. 次に s2 についての再帰で,環境 σ1 Γ の元で s1 の型は infer {m1} Γ s2 となるので, σ2(σ1Γ) ⊢ s2 : t2 なる σ2, t2 が得られる.
1. ここで得られた σ2 を 1. で得られた型判定に適用すると,
http://github.com/KDXU/InferAgda
application
30
4. 次に,σ2(t1) = t2 → β (ここで β は新しい型変数)となる unify σ3 を求めると, σ3(σ2(t1)) = σ3(t2 → β)
となり,σ3 を 2. で得られているふたつの型判定に適用すると, σ3 (σ2 (σ1 Γ)) ⊢ s1 : σ3 (σ2(t1)) σ3 (σ2(σ1 Γ)) ⊢ s2 : σ3 (t2)
が得られる.
http://github.com/KDXU/InferAgda
application
31
5.ここから関数適用の型規則を使うと,
σ3(σ2(σ1Γ)) ⊢ App s1 s2 : σ3(β)
が得られる.6.よって,App s1 s2 が返すものは
just (m2,σ3+(σ2+σ1), σ3(β))
となる.
http://github.com/KDXU/InferAgda
application
32
型変数の数を巧妙にとらえることがキーとなる
http://github.com/KDXU/InferAgda
application
33
❉ 型変数の数は,型推論が始まる時点では m である ❉ s1 を型推論する時点でそれは m+count s1 まで増えるが型 推論中に型変数の具体化が起こり m1 となる ❉ 次に,s2 を型推論する時点で m1+count s2 まで増えるがや はり型推論中に型変数の具体化が起こり m2 となる ❉ さらに,β を割り当てるので m2 + 1 個に増え, ❉ 最後の unification で m3 個に減ることになる
http://github.com/KDXU/InferAgda
well-typed な型推論• これまでの型推論器は停止性のみ保証している
• 正当性が保証された型推論をしたい
• well-typed な型を定義する
• 型規則を構文の定義に埋め込んでいる
34
http://github.com/KDXU/InferAgda
well-typed な型推論
• infer2 :型環境と well-scoped な項 s が入力となり,型変数の数 m′ と s の型 τ と必要な代入 σ と型付けされた well-typed な項を Maybe モナドに包んで返す関数
35
http://github.com/KDXU/InferAgda
variable• 変数の場合は単に型環境の型を返す
• Var x の型が微妙に違うので補助関数を用いて結んでいる
36
http://github.com/KDXU/InferAgda
lambda• まず body 部分の型推論を行い,body 部分に型が付いた場合のみ型付けされ,「引数の型⇒ body の型」が全体の型となる
37
http://github.com/KDXU/InferAgda
lambda• well-scoped の場合と同様,まず body 部分の型推論を行い,body 部分に型が付いた場合のみ型付けされ,「引数の型⇒ body の型」が全体の型となる
38
http://github.com/KDXU/InferAgda
lambda
39
• このコードは well-scoped な場合と比べて,再帰呼び出しの結果として well-typed な項を受け取っていることと,最後に well-typed な LamW を返している点のみが異なっている
• body 部分を型推論しwell-typed な w が得られた場合は最終的に返したい well-typed な項はほぼ Lam tx w となる
• しかし,実際に再帰呼び出しで得られた w の型が異なる
http://github.com/KDXU/InferAgda
lambda
40
wの型は展開すると
WellTypedTerm (tx :: substCxt σ (liftCxt (count s) (liftCxt 1 Γ))) t
であるが,実際に必要な型は
WellTypedTerm (tx :: substCxt σ (liftCxt (count (Lam s)) Γ)) t
http://github.com/KDXU/InferAgda
lambda
41
wの型は展開すると
WellTypedTerm (tx :: substCxt σ (liftCxt (count s) (liftCxt 1 Γ))) t
であるが,実際に必要な型は
WellTypedTerm (tx :: substCxt σ (liftCxt (count (Lam s)) Γ)) t
「環境部分」が異なっている
http://github.com/KDXU/InferAgda
lambda
42
この2つを eq という証明を用いて結んでいる
http://github.com/KDXU/InferAgda
application
43
well-scoped の時と同様,
1. App s1 s2 において,s1 の型推論によって返された代入 σ1 を Γ に加え,
2. 環境 σ1 Γ の元 での s2 の型推論が成功した場合にのみ,
3. (s2 の型⇒ β)が s1 と等しくなっているかどうかを unify 関数によって判定する
http://github.com/KDXU/InferAgda
application
44
http://github.com/KDXU/InferAgda45
• 再帰呼び出しの結果を w1, w2 に受け取っていることと最後に well- typed な AppW1W2 を返している点のみが異なっている
•AppW1W2 はほぼ App w1 w2 となる
•しかし lambda と同様に型の調整を行う必要がある
http://github.com/KDXU/InferAgda
application
46
WellTypedTerm (substCxt σ 3 (liftCxt 1 (substCxt σ 2 (liftCxt (count s2) (substCxt σ 1 (liftCxt (count s1) Γ)))))) (substType σ 3 (liftType 1 t2))
substWTerm σ 3 (liftWTerm 1 w2) の型は
となる.
http://github.com/KDXU/InferAgda
application
47
WellTypedTerm (substCxt σ 3 (liftCxt 1 (substCxt σ 2 (liftCxt (count s2) (substCxt σ 1 (liftCxt (count s1) Γ)))))) (substType σ 3 (liftType 1 t2))
substWTerm σ 3 (liftWTerm 1 w2) の型は
となる.これと w2’ を見比べると
w2’ : WellTypedTerm (substCxt (σ 3 +!+ (σ 2’ +!+ σ 1’)) (liftCxt (count (App s1 s2)) Γ)) (substType σ 3 (liftType 1 t2))
http://github.com/KDXU/InferAgda
application
48
WellTypedTerm (substCxt σ 3 (liftCxt 1 (substCxt σ 2 (liftCxt (count s2) (substCxt σ 1 (liftCxt (count s1) Γ)))))) (substType σ 3 (liftType 1 t2))
得られた w2 の型は
欲しい w2 の型は
w2’ : WellTypedTerm (substCxt (σ 3 +!+ (σ 2’ +!+ σ 1’)) (liftCxt (count (App s1 s2)) Γ)) (substType σ 3 (liftType 1 t2))
http://github.com/KDXU/InferAgda
すなわち1. (count s1) だけ型変数を増やし,σ1 を施す
2. (count s2) だけ型変数を増やし,σ2を施す
3. 1 だけ型変数を増やし,σ3を施す
49
1. 一度に (count (App s1 s2)) だけ型変数を増やし,(σ1 + (σ2 + σ3 )) を施す
を示す必要がある.
http://github.com/KDXU/InferAgda
すなわち1. (count s1) だけ型変数を増やし,σ1 を施す
2. (count s2) だけ型変数を増やし,σ2を施す
3. 1 だけ型変数を増やし,σ3を施す
50
1. 一度に (count (App s1 s2)) だけ型変数を増やし,(σ1 + (σ2 + σ3 )) を施す
この証明はかなり長くなるが示すことができる
http://github.com/KDXU/InferAgda
application
51
w1’ : WellTypedTerm (substCxt (σ 3 +!+ (σ 2’ +!+ σ 1’)) (liftCxt (count (App s1 s2)) Γ)) (substType σ 3 (liftType 1 t2 ⇒ TVar (fromN m2)))
w1 : WellTypedTerm (substCxt σ 1 (liftCxt (count s1) Γ)) t1
w1に関して,型は
w1’ の型は
http://github.com/KDXU/InferAgda52
w1’ : WellTypedTerm (substCxt (σ 3 +!+ (σ 2’ +!+ σ 1’)) (liftCxt (count (App s1 s2)) Γ)) (substType σ 3 (liftType 1 t2 ⇒ TVar (fromN m2)))
WellTypedTerm (substCxt σ 3 (liftCxt 1 (substCxt σ 2 (liftCxt (count s2) (substCxt σ 1 (liftCxt (count s1) Γ)))))) (substType σ 3 (liftType 1 (substType σ 2 (liftType (count s2) t1)))
w1 に対して一連の操作を行って得られた substWTerm σ 3 (liftWTerm 1 (substWTerm σ 2 (liftWTerm (count s2) w1)))
の型は
対して,欲しい型は
http://github.com/KDXU/InferAgda
application• w2 の場合と異なり,環境も型も異なる → 環境に関しては w2 と同じ方法で示せる
• 型に関して
53
substType σ 3 (liftType 1 (substType σ 2 (liftType (count s2) t1)))
substType σ 3 (liftType 1 t2 ⇒ TVar (fromN m2))
と
が同じであることを示す必要がある
http://github.com/KDXU/InferAgda
application
• 両者は unify 関数によって同じ型になっているはず
• これまでの unify 関数はそれを保証していない
• 2つの型が同値であることの証明がついた unify 関数が必要
54
substType σ3 (liftType 1 (substType σ2 (liftType (count s2) t1)))
substType σ3 (liftType 1 t2 ⇒ TVar (fromN m2))
http://github.com/KDXU/InferAgda
application
• 両者は unify 関数によって同じ型になっているはず
• これまでの unify 関数はそれを保証していない
• 2つの型が同値であることの証明がついた unify 関数が必要
55
σ3 (σ2 (t1))
σ3 (t2 ⇒ β)
http://github.com/KDXU/InferAgda
application
• 証明付きの unify を実装する必要がある→ unify2 関数とする
• その証明を用いて w2 と w2’ を結ぶ
• unify2 関数に関しては次に説明
56
http://github.com/KDXU/InferAgda
証明付きの unify• well-scoped な項の場合だと unify は「何か代入を返すもの」と定義されているのみで「返って来た代入が確かにふたつの型を単一化するか」は保証されていない
• well-typed な型推論器を実装するにおいて,ふたつの型が確かに等しいことを保証するような unification の関数が必要
• それにあたり,flexFlex,flexRigid なども変換前後における型の代入が等しいことの証明を添えて返すように変更する必要がある
• 新しい flexFlex2,flexRigid2 を定義する
57
http://github.com/KDXU/InferAgda
証明付きの unify
58
flexFlex2, flexRigid2 : σ が確かに与えられた 2 つの型変数,もしくは型を単一化するという証明を加えて返す
http://github.com/KDXU/InferAgda
証明付きの unify
59
これを利用して,amgu2 も
「substType したものが等しい」という証明に加えて, さらに amgu2 を定義する際の帰納法の仮定に必要となる
「返って来た σ が acc の拡張になっている」
という証明を加える
http://github.com/KDXU/InferAgda
証明付きの unify
60
unify2 t1 t2 :
型変数が m 個であるような型 t1 と t2 を単一化する代入と,その二つの substitution した型が等しい証明を返す
http://github.com/KDXU/InferAgda
証明付きの unify
61
well-typed な型推論器の実装においては well-scoped の場合と同じく型変数を巧妙に操作することに加えて,「 unify の拡張, flexFlex や flexRigid といった機構の定式化」も必要となった
http://github.com/KDXU/InferAgda
まとめ• Agda で simply-typed lambda calculus の unification の機構を McBride の手法を用い型推論器の実装を行った
• さらに型推論器が正しいことを示すために正当性の証明がついた型推論器を作成した
• 今後は syntax の拡張として多相の Let 文を含めた形についても実装と証明を行っていきたい
• 現在の実装だと where 節が冗長なため実行時間が比較的長くなってしまっており,かつ証明が煩雑なので実装の簡略化を試みていきたい
62