Sub: サブタイプ
Set Warnings "-notation-overridden,-parsing".
From Coq Require Import Strings.String.
From PLF Require Import Maps.
From PLF Require Import Types.
From PLF Require Import Smallstep.
さて、サブタイプ(subtyping)の学習に入ります。
サブタイプはオブジェクト指向プログラミングをサポートするのに重要な機能の一つです。
以下に定義する二つのレコード型が出現するプログラムを書いているとしましょう。
Person = {name:String, age:Nat} Student = {name:String, age:Nat, gpa:Nat}
レコードを持つ単純型付きラムダ計算では、項
しかしこれは馬鹿らしいことです。
実際には関数に、必要とされるものより良い引数を与えているのです!
この関数の本体がレコード引数rに対して行いうることは、そのフィールドageを射影することだけです。
型から許されることは他にはありません。
すると、他にgpaフィールドが存在するか否かは何の違いもないはずです。
これから、直観的に、この関数は少なくともageフィールドを持つ任意のレコード値に適用可能であるべきと思われます。
一般に、より豊かなフィールドを持つレコードは、そのサブセットのフィールドだけを持つレコードと「任意のコンテキストで少なくとも同等の良さである」と言えるでしょう。
より長い(多くのフィールドを持つ)レコード型の任意の値は、より短かいレコード型が求められるコンテキストで「安全に」(safely)使えるという意味においてです。
コンテキストがより短かい型のものを求めているときに、より長い型のものを与えた場合、何も悪いことは起こらないでしょう(形式的には、プログラムは行き詰まることはないでしょう)。
ここで働く一般原理はサブタイプ(付け)(subtyping)と呼ばれます。
型Tの値が求められている任意のコンテキストで型Sの値が安全に使えるとき、「SはTのサブタイプである」と言い、 S <: T と書きます。
サブタイプの概念はレコードだけではなく、関数、対、など言語のすべての型コンストラクタに適用されます。
Safe substitution principle:
(\r:Person. (r.age)+1) {name="Pat",age=21,gpa=1}は型付けできません。 なぜなら、これはフィールドが2つのレコードを引数としてとる関数に3つのフィールドを持つレコードが与えられている部分を含んでいて、一方、T_App規則は関数の定義域の型は引数の型に完全に一致することを要求するからです。
サブタイプは多くのプログラミング言語で重要な役割を演じます。
特に、オブジェクト指向言語のサブクラス(subclassing)の概念と近い関係にあります。
JavaやC#等のオブジェクトはレコードと考えることができます。
いくつかのフィールドは関数(「メソッド」)で、いくつかのフィールドはデータ値(「フィールド」または「インスタンス変数」)です。
オブジェクトoのメソッドmを引数a1..anのもとで呼ぶことは、大雑把に言うと、oからmフィールドを射影として抽出して、それをa1..anに適用することです。
オブジェクトの型は「クラス(class)」や、言語によっては「インターフェース(interface)」と呼ばれます。
この両者はどちらも、どのメソッドやどのデータフィールドをオブジェクトが提供するかを表しています。
クラスやインターフェースは、サブクラス(subclass)関係やサブインターフェース(subinterface)関係で関係づけられます。
サブクラス(またはサブインターフェース)に属するオブジェクトには、スーパークラス(またはスーパーインターフェース)に属するオブジェクトのメソッドとフィールドのすべての提供することが求められ、さらに追加で他のメソッドやフィールドを提供することもできます。
サブクラスのオブジェクトをスーパークラスのオブジェクトの場所で使えるという事実は、複雑なライブラリの構築にきわめて便利なほどの柔軟性を提供します。
例えば、Javaの Swingフレームワークのようなグラフィカルユーザーインターフェースツールキットは、スクリーンに表示できユーザとインタラクションできるグラフィカルな表現を持つすべてのオブジェクトに共通のフィールドとメソッドを集めた、抽象インターフェースComponentを定義するでしょう。
そのようなオブジェクトとしては、典型的なGUIのボタン、チェックボックス、スクロールバーなどがあります。
この共通インターフェースのみに依存するメソッドはこれらのどのオブジェクトにも適用できます。
もちろん、実際のオブジェクト指向言語はこれらに加えてたくさんの機能を持っています。
例えば、フィールドを更新できます。
フィールドやメソッドをprivateと宣言できます。
クラスにオブジェクトを作り上げるための「初期化子(initializer)」を定義できます。
サブクラスのコードが「継承(inheritance)」を通じてスーパークラスのコードと協調できます。
クラスが静的なメソッドやフィールドを持つことができます。
などなど、です。
ものごとを単純なまま進めるために、これらの問題を扱うことはしません。
実際、これ以上オブジェクトやクラスについて話すことはありません。
(もし興味があるなら、 Bib.v 内の Pierce 2002 にたくさんの議論があります。)
その代わり、STLCの単純化された設定のもとで、サブクラス/サブインターフェース関係の背後にある核となる概念について学習します。
この章のゴールは(MoreStlcの拡張機能を持つ)単純型付きラムダ計算にサブタイプを追加することです。
これは2つのステップから成ります:
2つ目のステップは実際はとても単純です。型付け関係にただ1つの規則だけを追加します。
その規則は、包摂規則(rule of subsumption)と呼ばれます:
- 型の間の二項サブタイプ関係(subtype relation)を定義します。
- 型付け関係をサブタイプを考慮した形に拡張します。
Gamma |- t \in S S <: T --------------------------- (T_Sub) Gamma |- t \in Tこの規則は、直観的には、項について知っている情報のいくらかを「忘れる」ようにしてよいと言っています。
例えば、tが2つのフィールドを持つレコード(例えば、S = {x:A->A, y:B->B})で、フィールドの1つを忘れることにした(T = {y:B->B})とします。
するとtを、1フィールドのレコードを引数としてとる関数に渡すことができるようになります。
まず最初に、2つの「構造規則」("structural rules")を追加します。
これらは特定の型コンストラクタからは独立したものです。
推移(transitivity)規則は、直観的には、SがUよりも(安全、豊かさの面で)良く、UがTよりも良ければ、SはTよりも良いというものです。
S <: U U <: T ---------------- (S_Trans) S <: Tそして反射(reflexivity)規則は、任意の型はそれ自身と同じ良さを持つというものです。
------ (S_Refl) T <: T
次に、直積型です。
ある一つの対が他の対のサブタイプであるとは、それぞれの成分が対応するもののサブタイプであることです。
S1 <: T1 S2 <: T2 -------------------- (S_Prod) S1 * S2 <: T1 * T2
関数型のサブタイプ関係はやや直観から外れます。
次の型を持つ2つの関数fとgがあるとします:
f : C -> Student
g : (C->Person) -> D
つまり、fは型Studentのレコードを返す関数であり、gは引数として、型Personのレコードを返す関数をとる高階関数です。
そして、Student が Person のサブタイプであるとします。
すると、関数適用 g f は、両者の型が正確に一致しないにもかかわらず安全です。
なぜなら、gがfについて行うことができるのは、fを(型Cの)ある引数に適用することだけだからです。
その結果は実際にはStudent型のレコード値になります。
ここでgが期待するのはPerson型のレコードです。
しかしこれは安全です。なぜならgがすることができるのは、わかっている2つのフィールド(nameとage)を射影することだけで、それは存在するフィールドの一部だからです。
この例から、関数型のサブタイプ規則が以下のようになるべきということが導かれます。
2つの関数型がサブタイプ関係にあるのは、その結果が次の条件のときです:
このことを表す例を見てみましょう。
f : Person -> C
g : (Student -> C) -> D
関数適用g fは安全です。
なぜなら、gの本体でのfの使い方は、Student型の引数に適用することだけだからです。
fは(少なくとも)Personのもつフィールドを含んだレコードを要求するだけなので、これはうまく動きます。
よって、StudentがPersonのサブタイプであるため、Person -> CはStudent -> Cのサブタイプとなります。
直観的には、型S1->S2の関数fがあるとき、fは型S1の要素を引数として許容することがわかります。
明らかにfはS1の任意のサブタイプT1の要素も引数として許容します。
fの型は同時にfが型S2の要素を返すことも示しています。
この値がS2の任意のスーパータイプT2に属することも見ることができます。
つまり、型S1->S2の任意の関数fは、型T1->T2を持つと見ることもできるということです。
f : C -> Student
g : (C->Person) -> D
S2 <: T2 ---------------- (S_Arrow_Co) S1 -> S2 <: S1 -> T2同様に、これを一般化して、2つの関数型のサブタイプ関係を、引数の条件を含めた形にすることができます:
T1 <: S1 S2 <: T2 -------------------- (S_Arrow) S1 -> S2 <: T1 -> T2ここで注意するのは、引数の型はサブタイプ関係が逆向きになることです。 S1->S2 が T1->T2 のサブタイプであると結論づけるには、T1がS1のサブタイプでなければなりません。 関数型のコンストラクタは最初の引数について反変(contravariant)であり、 二番目の引数について共変(covariant)であると言います。
f : Person -> C
g : (Student -> C) -> D
レコード型のサブタイプは何でしょうか?
基本的直観は、「より小さな」レコードの場所で「より大きな」レコードを使うことは常に安全だということです。
つまり、レコード型があるとき、さらにフィールドを追加したものは常にサブタイプになるということです。
もしあるコードがフィールドxとyを持つレコードを期待していたとき、レコードx、y、zを持つレコードを受けとることは完璧に安全です。
zフィールドは単に無視されるだけです。
例えば次の通りです。
{name:String, age:Nat, gpa:Nat} <: {name:String, age:Nat}
{name:String, age:Nat} <: {name:String}
{name:String} <: {}
これはレコードの「幅サブタイプ(width subtyping)」として知られます。
{name:String, age:Nat, gpa:Nat} <: {name:String, age:Nat}
{name:String, age:Nat} <: {name:String}
{name:String} <: {}
レコードの1つのフィールドの型をそのサブタイプで置き換えることでも、レコードのサブタイプを作ることができます。
もしあるコードが型Tのフィールドxを持つレコードを期待するとき、型Sが型Tのサブタイプである限り、型Sのフィールドxを持つレコードが来ても何の問題も起こりません。
例えば次の通りです。
{x:Student} <: {x:Person}
これは「深さサブタイプ(depth subtyping)」として知られています。
{x:Student} <: {x:Person}
最後に、レコードのフィールドは特定の順番で記述されますが、その順番は実際には問題ではありません。
例えば次の通りです。
{name:String,age:Nat} <: {age:Nat,name:String}
これは「順列サブタイプ(permutation subtyping)」として知られています。
{name:String,age:Nat} <: {age:Nat,name:String}
これらをレコードについての単一のサブタイプ規則に形式化することもできはします。
次の通りです:
しかしながら、この規則はかなり重くて読むのが大変なので、3つの簡単な規則に分解されます。
この3つをS_Transを使って結合することで、同じように作用させることができます。
forall jk in j1..jn, exists ip in i1..im, such that jk=ip and Sp <: Tk ---------------------------------- (S_Rcd) {i1:S1...im:Sm} <: {j1:T1...jn:Tn}つまり、左のレコードは(少なくとも)右のレコードのフィールドラベルをすべて持ち、両者で共通するフィールドはサブタイプ関係になければならない、ということです。
最初に、レコード型の最後にフィールドを追加したものはサブタイプになります。
n > m --------------------------------- (S_RcdWidth) {i1:T1...in:Tn} <: {i1:T1...im:Tm}S_RcdWidthを使うと、複数のフィールドを持つレコードについて、 前方のフィールドを残したまま後方のフィールドを取り除くことができます。 この規則で例えば {age:Nat,name:String} <: {age:Nat} を示せます。
二つ目では、レコード型の構成要素の内部にサブタイプ規則を適用できます。
S1 <: T1 ... Sn <: Tn ---------------------------------- (S_RcdDepth) {i1:S1...in:Sn} <: {i1:T1...in:Tn}例えば S_RcdDepthとS_RcdWidthを両方使って {y:Student, x:Nat} <: {y:Person}を示すことができます。
三つ目に、サブタイプとしてフィールドの並べ替えを可能にします。
例えば {name:String, gpa:Nat, age:Nat} <: Person であってほしいのです。
(これはまだ達成されていません。
S_RcdDepthとS_RcdWidthだけでは、レコード型の「後」からフィールドを落とすことしかできません。)
そのため、次の規則が必要です。
{i1:S1...in:Sn} is a permutation of {j1:T1...jn:Tn} --------------------------------------------------- (S_RcdPerm) {i1:S1...in:Sn} <: {j1:T1...jn:Tn}
なお、本格的な言語ではこれらのサブタイプ規則のすべてを採用しているとは限らないことは注記しておくべきでしょう。
例えばJavaでは
- 各クラスのメンバー(フィールドまたはメソッド)は1つだけインデックスを持ち、サブクラスでメンバーが追加されるたびに新しいインデックスが「右に」追加されます
(つまり、クラスには並び換えがありません)。
- クラスは複数のインターフェースを実装できます。
これはインターフェースの「多重継承(multiple inheritance)」と呼ばれます
(つまり、インターフェースには並び換えがあります)。
- 昔のバージョンでは、サブクラスではスーパークラスのメソッドの引数または結果の型を変えることはできませんでした (つまり、depth subtypingまたは関数型サブタイプのいずれかができなかったということです。 どちらかは見方によります)。
練習問題: ★★, standard, recommended (arrow_sub_wrong)
S1 <: T1 S2 <: T2 -------------------- (S_Arrow_wrong) S1 -> S2 <: T1 -> T22つの関数fとgについて、以下の型
f : Student -> Nat
g : (Person -> Nat) -> Nat
☐
最後に、サブタイプ関係の最大要素を定めます。
他のすべての型のスーパータイプであり、すべての(型付けできる)値が属する型です。
このために言語に新しい一つの型定数Topを追加し、Topをサブタイプ関係の他のすべての型のスーパータイプとするサブタイプ規則を定めます:
-------- (S_Top) S <: TopTop型はJavaやCにおける[Object]型に対応するものです。
Summary
- adding a base type Top,
- adding the rule of subsumption
- -------------------------- (T_Sub)
Gamma |- t \in T
- -------------------------- (T_Sub)
Gamma |- t \in T
- defining a subtype relation as follows:
- --------------- (S_Trans)
S <: T
- ----- (S_Refl)
- ------- (S_Top)
- ------------------- (S_Prod)
S1 * S2 <: T1 * T2
- ------------------- (S_Arrow)
- -------------------------------- (S_RcdWidth)
- --------------------------------- (S_RcdDepth)
- -------------------------------------------------- (S_RcdPerm) {i1:S1...in:Sn} <: {j1:T1...jn:Tn}
- --------------- (S_Trans)
S <: T
Exercises
Exercise: 1 star, standard, optional (subtype_instances_tf_1)
- T->S <: T->S
- Top->U <: S->Top
- (C->C) -> (A*B) <: (C->C) -> (Top*B)
- T->T->U <: S->S->V
- (T->T)->U <: (S->S)->V
- ((T->S)->T)->U <: ((S->T)->S)->V
- S*V <: T*U
Exercise: 2 stars, standard (subtype_order)
☐
Which of the following statements are true? Write true or
false after each one.
forall S T,
S <: T ->
S->S <: T->T
forall S,
S <: A->A ->
exists T,
S = T->T /\ T <: A
forall S T1 T2,
(S <: T1 -> T2) ->
exists S1 S2,
S = S1 -> S2 /\ T1 <: S1 /\ S2 <: T2
exists S,
S <: S->S
exists S,
S->S <: S
forall S T1 T2,
S <: T1*T2 ->
exists S1 S2,
S = S1*S2 /\ S1 <: T1 /\ S2 <: T2
Exercise: 1 star, standard (subtype_instances_tf_2)
☐
Which of the following statements are true, and which are false?
Exercise: 1 star, standard (subtype_concepts_tf)
- There exists a type that is a supertype of every other type.
- There exists a type that is a subtype of every other type.
- There exists a pair type that is a supertype of every other
pair type.
- There exists a pair type that is a subtype of every other
pair type.
- There exists an arrow type that is a supertype of every other
arrow type.
- There exists an arrow type that is a subtype of every other
arrow type.
- There is an infinite descending chain of distinct types in the
subtype relation---that is, an infinite sequence of types
S0, S1, etc., such that all the Si's are different and
each S(i+1) is a subtype of Si.
- There is an infinite ascending chain of distinct types in the subtype relation---that is, an infinite sequence of types S0, S1, etc., such that all the Si's are different and each S(i+1) is a supertype of Si.
☐
Is the following statement true or false? Briefly explain your
answer. (Here Base n stands for a base type, where n is
a string standing for the name of the base type. See the
Syntax section below.)
forall T,
~(T = Bool \/ exists n, T = Base n) ->
exists S,
S <: T /\ S <> T
Exercise: 2 stars, standard (proper_subtypes)
☐
Exercise: 2 stars, standard (small_large_1)
- What is the smallest type T ("smallest" in the subtype
relation) that makes the following assertion true? (Assume we
have Unit among the base types and unit as a constant of this
type.)
- What is the largest type T that makes the same assertion true?
☐
Exercise: 2 stars, standard (small_large_2)
- What is the smallest type T that makes the following
assertion true?
- What is the largest type T that makes the same assertion true?
☐
Exercise: 2 stars, standard, optional (small_large_3)
- What is the smallest type T that makes the following
assertion true?
- What is the largest type T that makes the same assertion true?
Exercise: 2 stars, standard (small_large_4)
- What is the smallest type T that makes the following
assertion true?
- What is the largest type T that makes the same assertion true?
☐
What is the smallest type T that makes the following
assertion true?
exists S t,
empty |- (\x:T. x x) t \in S
Exercise: 2 stars, standard (smallest_1)
☐
What is the smallest type T that makes the following
assertion true?
empty |- (\x:Top. x) ((\z:A.z) , (\z:B.z)) \in T
Exercise: 2 stars, standard (smallest_2)
☐
How many supertypes does the record type {x:A, y:C->C} have? That is,
how many different types T are there such that {x:A, y:C->C} <:
T? (We consider two types to be different if they are written
differently, even if each is a subtype of the other. For example,
{x:A,y:B} and {y:B,x:A} are different.)
☐
The subtyping rule for product types
S1 <: T1 S2 <: T2
(S_Prod) S1*S2 <: T1*T2
intuitively corresponds to the "depth" subtyping rule for records.
Extending the analogy, we might consider adding a "permutation" rule
T1*T2 <: T2*T1
for products. Is this a good idea? Briefly explain why or why not.
Exercise: 3 stars, standard, optional (count_supertypes)
Exercise: 2 stars, standard (pair_permutation)
(S_Prod) S1*S2 <: T1*T2
T1*T2 <: T2*T1
☐
上記の議論で使った、形式化する定義のほとんど -- 特に言語の構文と操作的意味 -- は前の章で見たものと同じです。
ここで必要となるものは型付け規則の包括規則による拡張と、サブタイプ関係を表す帰納的定義の追加です。
最初に同じ部分をやってみましょう。
この章の残りでは、レコード型は省略して、基底型、ブール型、関数型、UnitとTopのみ形式化し、直積型は練習問題にします。
例をおもしろくするために、String、Floatのような、任意の基底型を許しています。
(これはあくまで例で用いるためだけなので、わざわざこれらへの操作を追加したりはしませんが、そうすることは簡単です。)
Inductive ty : Type :=
| Top : ty
| Bool : ty
| Base : string -> ty
| Arrow : ty -> ty -> ty
| Unit : ty
.
Inductive tm : Type :=
| var : string -> tm
| app : tm -> tm -> tm
| abs : string -> ty -> tm -> tm
| tru : tm
| fls : tm
| test : tm -> tm -> tm -> tm
| unit : tm
.
置換の定義はSTLCと全く同じです。
Fixpoint subst (x:string) (s:tm) (t:tm) : tm :=
match t with
| var y =>
if eqb_string x y then s else t
| abs y T t1 =>
abs y T (if eqb_string x y then t1 else (subst x s t1))
| app t1 t2 =>
app (subst x s t1) (subst x s t2)
| tru =>
tru
| fls =>
fls
| test t1 t2 t3 =>
test (subst x s t1) (subst x s t2) (subst x s t3)
| unit =>
unit
end.
Notation "'[' x ':=' s ']' t" := (subst x s t) (at level 20).
Inductive value : tm -> Prop :=
| v_abs : forall x T t,
value (abs x T t)
| v_true :
value tru
| v_false :
value fls
| v_unit :
value unit
.
Hint Constructors value.
Reserved Notation "t1 '-->' t2" (at level 40).
Inductive step : tm -> tm -> Prop :=
| ST_AppAbs : forall x T t12 v2,
value v2 ->
(app (abs x T t12) v2) --> [x:=v2]t12
| ST_App1 : forall t1 t1' t2,
t1 --> t1' ->
(app t1 t2) --> (app t1' t2)
| ST_App2 : forall v1 t2 t2',
value v1 ->
t2 --> t2' ->
(app v1 t2) --> (app v1 t2')
| ST_TestTrue : forall t1 t2,
(test tru t1 t2) --> t1
| ST_TestFalse : forall t1 t2,
(test fls t1 t2) --> t2
| ST_Test : forall t1 t1' t2 t3,
t1 --> t1' ->
(test t1 t2 t3) --> (test t1' t2 t3)
where "t1 '-->' t2" := (step t1 t2).
Hint Constructors step.
ようやくおもしろい所にやって来ました。
サブタイプ関係の定義から始め、その重要な技術的性質を調べます。
サブタイプの定義は、動機付けの議論のところで概観した通りです。
Reserved Notation "T '<:' U" (at level 40).
Inductive subtype : ty -> ty -> Prop :=
| S_Refl : forall T,
T <: T
| S_Trans : forall S U T,
S <: U ->
U <: T ->
S <: T
| S_Top : forall S,
S <: Top
| S_Arrow : forall S1 S2 T1 T2,
T1 <: S1 ->
S2 <: T2 ->
(Arrow S1 S2) <: (Arrow T1 T2)
where "T '<:' U" := (subtype T U).
なお、基底型(BoolとBase)について、特別な規則は何ら必要ありません。
基底型は自動的に(S_Reflより)自分自身のサブタイプであり、(S_Topより)Topのサブタイプでもあります。
そしてこれが必要な全てです。
Hint Constructors subtype.
Module Examples.
Open Scope string_scope.
Notation x := "x".
Notation y := "y".
Notation z := "z".
Notation A := (Base "A").
Notation B := (Base "B").
Notation C := (Base "C").
Notation String := (Base "String").
Notation Float := (Base "Float").
Notation Integer := (Base "Integer").
Example subtyping_example_0 :
(Arrow C Bool) <: (Arrow C Top).
Proof. auto. Qed.
練習問題: ★★, standard, optional (subtyping_judgements)
Person := { name : String }
Student := { name : String ; gpa : Float }
Employee := { name : String ; ssn : Integer }
Definition Person : ty
. Admitted.
Definition Student : ty
. Admitted.
Definition Employee : ty
. Admitted.
. Admitted.
Definition Student : ty
. Admitted.
Definition Employee : ty
. Admitted.
それらの型を用いて、以下のサブタイプ関係を示しなさい。
Example sub_student_person :
Student <: Person.
Proof.
Admitted.
Example sub_employee_person :
Employee <: Person.
Proof.
Admitted.
☐
以下の事実のほとんどは、Coqで証明するのは簡単です。
練習問題の効果を十分に得るために、どうやって証明するか自分が理解していることを紙に証明を書いて確認しなさい。
Example subtyping_example_1 :
(Arrow Top Student) <: (Arrow (Arrow C C) Person).
Proof with eauto.
Admitted.
(Arrow Top Student) <: (Arrow (Arrow C C) Person).
Proof with eauto.
Admitted.
☐
☐
型付け関係の変更は、包摂規則 T_Sub の追加だけです。
Definition context := partial_map ty.
Reserved Notation "Gamma '|-' t '\in' T" (at level 40).
Inductive has_type : context -> tm -> ty -> Prop :=
| T_Var : forall Gamma x T,
Gamma x = Some T ->
Gamma |- var x \in T
| T_Abs : forall Gamma x T11 T12 t12,
(x |-> T11 ; Gamma) |- t12 \in T12 ->
Gamma |- abs x T11 t12 \in Arrow T11 T12
| T_App : forall T1 T2 Gamma t1 t2,
Gamma |- t1 \in Arrow T1 T2 ->
Gamma |- t2 \in T1 ->
Gamma |- app t1 t2 \in T2
| T_True : forall Gamma,
Gamma |- tru \in Bool
| T_False : forall Gamma,
Gamma |- fls \in Bool
| T_Test : forall t1 t2 t3 T Gamma,
Gamma |- t1 \in Bool ->
Gamma |- t2 \in T ->
Gamma |- t3 \in T ->
Gamma |- test t1 t2 t3 \in T
| T_Unit : forall Gamma,
Gamma |- unit \in Unit
| T_Sub : forall Gamma t S T,
Gamma |- t \in S ->
S <: T ->
Gamma |- t \in T
where "Gamma '|-' t '\in' T" := (has_type Gamma t T).
Hint Constructors has_type.
The following hints help auto and eauto construct typing
derivations. They are only used in a few places, but they give
a nice illustration of what auto can do with a bit more
programming. See chapter UseAuto for more on hints.
Hint Extern 2 (has_type _ (app _ _) _) =>
eapply T_App; auto.
Hint Extern 2 (_ = _) => compute; reflexivity.
Module Examples2.
Import Examples.
以下の練習問題は言語に直積を追加した後に行いなさい。
それぞれの非形式的な型付けジャッジメントについて、Coqで形式的主張を記述し、それを証明しなさい。
チェックしたいシステムの根本的性質はいつもと同じく、進行と保存です。
STLCに参照を拡張したもの(References章)とは違い、サブタイプを考慮しても、これらの主張を変化させる必要はありません。
ただし、それらの証明はもうちょっと複雑になります。
これらは反転補題(inversion lemma)と呼ばれます。
これは、証明で組込みのinversionタクティックに似た役目をするためです。
つまり、サブタイプ関係の主張 S <: T の導出が存在するという仮定と、SやTの形についてのいくつかの制約が与えられたとき、それぞれの反転補題は、S <: T の導出がどのような形をしていなければならないか、を提示することで、S や T のより詳細な情報、および S <: T が(矛盾なく)存在することを述べます。
Lemma sub_inversion_Bool : forall U,
U <: Bool ->
U = Bool.
Proof with auto.
intros U Hs.
remember Bool as V.
Admitted.
U <: Bool ->
U = Bool.
Proof with auto.
intros U Hs.
remember Bool as V.
Admitted.
☐
Lemma sub_inversion_arrow : forall U V1 V2,
U <: Arrow V1 V2 ->
exists U1 U2,
U = Arrow U1 U2 /\ V1 <: U1 /\ U2 <: V2.
Proof with eauto.
intros U V1 V2 Hs.
remember (Arrow V1 V2) as V.
generalize dependent V2. generalize dependent V1.
Admitted.
U <: Arrow V1 V2 ->
exists U1 U2,
U = Arrow U1 U2 /\ V1 <: U1 /\ U2 <: V2.
Proof with eauto.
intros U V1 V2 Hs.
remember (Arrow V1 V2) as V.
generalize dependent V2. generalize dependent V1.
Admitted.
☐
進行定理(値でない型付け可能な項の計算は進められる)の証明はそれほど大きく変える必要はありません。
1つだけ小さなリファインメントが必要です。
問題となる項が関数適用 t1 t2 でt1とt2が両方とも値の場合を考えるとき、t1がラムダ抽象の形をしており、そのためST_AppAbs簡約規則が適用できることを確認する必要があります。
もともとのSTLCでは、これは明らかです。
t1が関数型T11->T12を持ち、また、関数型の値を与える規則が1つだけ、つまり規則T_Absだけであり、そしてこの規則の結論部の形から、t1は関数型にならざるを得ない、ということがわかります。
サブタイプを持つSTLCにおいては、この推論はそのままうまく行くわけではありません。
その理由は、値が関数型を持つことを示すのに使える規則がもう1つあるからです。
包摂規則です。
幸い、このことが大きな違いをもたらすことはありません。
もし Gamma |- t1 \in T11->T12 を示すのに使われた最後の規則が包摂規則だった場合、導出のその前の部分で、同様にt1が主部(項の部分)である導出があり、帰納法により一番最初にはT_Absが使われたことが推論できるからです。
推論のこの部分は次の補題にまとめられています。
この補題は、関数型の可能な「正準形(canonical forms、つまり値)」を示します。
Lemma canonical_forms_of_arrow_types : forall Gamma s T1 T2,
Gamma |- s \in Arrow T1 T2 ->
value s ->
exists x S1 s2,
s = abs x S1 s2.
Proof with eauto.
Admitted.
Gamma |- s \in Arrow T1 T2 ->
value s ->
exists x S1 s2,
s = abs x S1 s2.
Proof with eauto.
Admitted.
☐
Lemma canonical_forms_of_Bool : forall Gamma s,
Gamma |- s \in Bool ->
value s ->
s = tru \/ s = fls.
Proof with eauto.
intros Gamma s Hty Hv.
remember Bool as T.
induction Hty; try solve_by_invert...
-
subst. apply sub_inversion_Bool in H. subst...
Qed.
進行性の証明は純粋なSTLCとほぼ同様に進みます。
ただ何箇所かで正準形補題を使うことを除けば...
「定理」(進行): 任意の項tと型Tについて、もし empty |- t \in T ならばtは値であるか、ある項t'について t --> t' である。
「証明」:tとTが与えられ、empty |- t \in T とする。
型付けの導出についての帰納法で進める。
(最後の規則が)T_Abs、T_Unit、T_True、T_Falseのいずれかの場合は自明である。
なぜなら、関数抽象、unit、tru、flsは既に値だからである。
T_Varであることはありえない。
なぜなら、変数は空コンテキストで型付けできないからである。
残るのはより興味深い場合である:
形式的には:
- 型付け導出の最後のステップで規則T_Appが使われた場合、項t1、t2と型T1、T2が存在して t = t1 t2、T = T2、empty |- t1 \in T1 -> T2、empty |- t2 \in T1 となる。
さらに帰納法の仮定から、t1は値であるかステップを進めることができ、t2も値であるかステップを進めることができる。
このとき、3つの場合がある:
- 導出の最後のステップで規則T_Testが使われた場合、項t1、t2、t3があって t = test t1 then t2 else t3 となり、 empty |- t1 \in Bool かつ empty |- t2 \in T かつ empty |- t3 \in T である。
さらに、帰納法の仮定よりt1は値であるかステップを進めることができる。
- 導出の最後のステップが規則T_Subによる場合、型Sがあって S <: T かつ empty |- t \in S となっている。 求める結果は型付け導出の帰納法の仮定そのものである。
Theorem progress : forall t T,
empty |- t \in T ->
value t \/ exists t', t --> t'.
Proof with eauto.
intros t T Ht.
remember empty as Gamma.
revert HeqGamma.
induction Ht;
intros HeqGamma; subst...
-
inversion H.
-
right.
destruct IHHt1; subst...
+
destruct IHHt2; subst...
*
destruct (canonical_forms_of_arrow_types empty t1 T1 T2)
as [x [S1 [t12 Heqt1]]]...
subst. exists ([x:=t2]t12)...
*
inversion H0 as [t2' Hstp]. exists (app t1 t2')...
+
inversion H as [t1' Hstp]. exists (app t1' t2)...
-
right.
destruct IHHt1.
+ eauto.
+ assert (t1 = tru \/ t1 = fls)
by (eapply canonical_forms_of_Bool; eauto).
inversion H0; subst...
+ inversion H. rename x into t1'. eauto.
Qed.
保存定理の証明はサブタイプを追加したことでやはり少し複雑になります。
その理由は、上述の「サブタイプの反転補題」と同様に、純粋なSTLCでは定義から明らかであった(形式的には、inversionタクティックからすぐに得られた)性質が、サブタイプの存在で本当の証明を必要とするようになったためです。
サブタイプがある場合、同じhas_typeの主張を導出するのに複数の方法があるのです。
以下の反転補題は、もし関数抽象の型付け主張 Gamma |- \x:S1.t2 \in T の導出があるならば、その導出の中に本体t2の型を与える部分が含まれている、ということを言うものです。
「補題」: もし Gamma |- \x:S1.t2 \in T ならば、型S2が存在して x|->S1; Gamma |- t2 \in S2 かつ S1 -> S2 <: T となる。
(この補題は「Tはそれ自身が関数型である」とは言っていないことに注意します。
そうしたいところですが、それは成立しません!)
「証明」:Gamma、x、S1、t2、Tを補題の主張に記述された通りとする。
Gamma |- \x:S1.t2 \in T の導出についての帰納法で証明する。
T_VarとT_Appの場合はあり得ない。
これらは構文的に関数抽象の形の項に型を与えることはできないからである。
形式的には:
- 導出の最後のステップ使われた規則がT_Absの場合、型T12が存在して T = S1 -> T12 かつ x:S1; Gamma |- t2 \in T12 である。
S2としてT12をとると、S_Reflより S1 -> T12 <: S1 -> T12 となり、求める性質が成立する。
- 導出の最後のステップ使われた規則がT_Subの場合、型Sが存在して S <: T かつ Gamma |- \x:S1.t2 \in S となる。 型付け導出の帰納仮定より、型S2が存在して S1 -> S2 <: S かつ Gamma, x:S1 |- t2 \in S2 である。 このS2を採用すれば、 S1 -> S2 <: T であるからS_Transより求める性質が成立する。
Lemma typing_inversion_abs : forall Gamma x S1 t2 T,
Gamma |- (abs x S1 t2) \in T ->
exists S2,
Arrow S1 S2 <: T
/\ (x |-> S1 ; Gamma) |- t2 \in S2.
Proof with eauto.
intros Gamma x S1 t2 T H.
remember (abs x S1 t2) as t.
induction H;
inversion Heqt; subst; intros; try solve_by_invert.
-
exists T12...
-
destruct IHhas_type as [S2 [Hsub Hty]]...
Qed.
同様に...
Lemma typing_inversion_var : forall Gamma x T,
Gamma |- (var x) \in T ->
exists S,
Gamma x = Some S /\ S <: T.
Proof with eauto.
intros Gamma x T Hty.
remember (var x) as t.
induction Hty; intros;
inversion Heqt; subst; try solve_by_invert.
-
exists T...
-
destruct IHHty as [U [Hctx HsubU]]... Qed.
Lemma typing_inversion_app : forall Gamma t1 t2 T2,
Gamma |- (app t1 t2) \in T2 ->
exists T1,
Gamma |- t1 \in (Arrow T1 T2) /\
Gamma |- t2 \in T1.
Proof with eauto.
intros Gamma t1 t2 T2 Hty.
remember (app t1 t2) as t.
induction Hty; intros;
inversion Heqt; subst; try solve_by_invert.
-
exists T1...
-
destruct IHHty as [U1 [Hty1 Hty2]]...
Qed.
Lemma typing_inversion_true : forall Gamma T,
Gamma |- tru \in T ->
Bool <: T.
Proof with eauto.
intros Gamma T Htyp. remember tru as tu.
induction Htyp;
inversion Heqtu; subst; intros...
Qed.
Lemma typing_inversion_false : forall Gamma T,
Gamma |- fls \in T ->
Bool <: T.
Proof with eauto.
intros Gamma T Htyp. remember fls as tu.
induction Htyp;
inversion Heqtu; subst; intros...
Qed.
Lemma typing_inversion_if : forall Gamma t1 t2 t3 T,
Gamma |- (test t1 t2 t3) \in T ->
Gamma |- t1 \in Bool
/\ Gamma |- t2 \in T
/\ Gamma |- t3 \in T.
Proof with eauto.
intros Gamma t1 t2 t3 T Hty.
remember (test t1 t2 t3) as t.
induction Hty; intros;
inversion Heqt; subst; try solve_by_invert.
-
auto.
-
destruct (IHHty H0) as [H1 [H2 H3]]...
Qed.
Lemma typing_inversion_unit : forall Gamma T,
Gamma |- unit \in T ->
Unit <: T.
Proof with eauto.
intros Gamma T Htyp. remember unit as tu.
induction Htyp;
inversion Heqtu; subst; intros...
Qed.
型付けについての反転補題と関数型の間のサブタイプの反転補題は「結合補題(combination lemma)」としてまとめることができます。
この補題は以下で実際に必要になるものを示します。
Lemma abs_arrow : forall x S1 s2 T1 T2,
empty |- (abs x S1 s2) \in (Arrow T1 T2) ->
T1 <: S1
/\ (x |-> S1 ; empty) |- s2 \in T2.
Proof with eauto.
intros x S1 s2 T1 T2 Hty.
apply typing_inversion_abs in Hty.
inversion Hty as [S2 [Hsub Hty1]].
apply sub_inversion_arrow in Hsub.
inversion Hsub as [U1 [U2 [Heq [Hsub1 Hsub2]]]].
inversion Heq; subst... Qed.
コンテキスト不変性補題は、純粋なSTLCと同じパターンをとります。
Inductive appears_free_in : string -> tm -> Prop :=
| afi_var : forall x,
appears_free_in x (var x)
| afi_app1 : forall x t1 t2,
appears_free_in x t1 -> appears_free_in x (app t1 t2)
| afi_app2 : forall x t1 t2,
appears_free_in x t2 -> appears_free_in x (app t1 t2)
| afi_abs : forall x y T11 t12,
y <> x ->
appears_free_in x t12 ->
appears_free_in x (abs y T11 t12)
| afi_test1 : forall x t1 t2 t3,
appears_free_in x t1 ->
appears_free_in x (test t1 t2 t3)
| afi_test2 : forall x t1 t2 t3,
appears_free_in x t2 ->
appears_free_in x (test t1 t2 t3)
| afi_test3 : forall x t1 t2 t3,
appears_free_in x t3 ->
appears_free_in x (test t1 t2 t3)
.
Hint Constructors appears_free_in.
Lemma context_invariance : forall Gamma Gamma' t S,
Gamma |- t \in S ->
(forall x, appears_free_in x t -> Gamma x = Gamma' x) ->
Gamma' |- t \in S.
Proof with eauto.
intros. generalize dependent Gamma'.
induction H;
intros Gamma' Heqv...
-
apply T_Var... rewrite <- Heqv...
-
apply T_Abs... apply IHhas_type. intros x0 Hafi.
unfold update, t_update. destruct (eqb_stringP x x0)...
-
apply T_Test...
Qed.
Lemma free_in_context : forall x t T Gamma,
appears_free_in x t ->
Gamma |- t \in T ->
exists T', Gamma x = Some T'.
Proof with eauto.
intros x t T Gamma Hafi Htyp.
induction Htyp;
subst; inversion Hafi; subst...
-
destruct (IHHtyp H4) as [T Hctx]. exists T.
unfold update, t_update in Hctx.
rewrite <- eqb_string_false_iff in H2.
rewrite H2 in Hctx... Qed.
置換補題(substitution lemma)は純粋なSTLCと同じ流れで証明されます。
唯一の大きな変更点は、いくつかの場所で、
部分項が型を持つことについての仮定から構造的情報を抽出するために、
Coqのinversionタクティックを使う代わりに上で証明した反転補題を使う必要があることです。
Lemma substitution_preserves_typing : forall Gamma x U v t S,
(x |-> U ; Gamma) |- t \in S ->
empty |- v \in U ->
Gamma |- [x:=v]t \in S.
Proof with eauto.
intros Gamma x U v t S Htypt Htypv.
generalize dependent S. generalize dependent Gamma.
induction t; intros; simpl.
-
rename s into y.
destruct (typing_inversion_var _ _ _ Htypt)
as [T [Hctx Hsub]].
unfold update, t_update in Hctx.
destruct (eqb_stringP x y) as [Hxy|Hxy]; eauto;
subst.
inversion Hctx; subst. clear Hctx.
apply context_invariance with empty...
intros x Hcontra.
destruct (free_in_context _ _ S empty Hcontra)
as [T' HT']...
inversion HT'.
-
destruct (typing_inversion_app _ _ _ _ Htypt)
as [T1 [Htypt1 Htypt2]].
eapply T_App...
-
rename s into y. rename t into T1.
destruct (typing_inversion_abs _ _ _ _ _ Htypt)
as [T2 [Hsub Htypt2]].
apply T_Sub with (Arrow T1 T2)... apply T_Abs...
destruct (eqb_stringP x y) as [Hxy|Hxy].
+
eapply context_invariance...
subst.
intros x Hafi. unfold update, t_update.
destruct (eqb_string y x)...
+
apply IHt. eapply context_invariance...
intros z Hafi. unfold update, t_update.
destruct (eqb_stringP y z)...
subst.
rewrite <- eqb_string_false_iff in Hxy. rewrite Hxy...
-
assert (Bool <: S)
by apply (typing_inversion_true _ _ Htypt)...
-
assert (Bool <: S)
by apply (typing_inversion_false _ _ Htypt)...
-
assert ((x |-> U ; Gamma) |- t1 \in Bool
/\ (x |-> U ; Gamma) |- t2 \in S
/\ (x |-> U ; Gamma) |- t3 \in S)
by apply (typing_inversion_if _ _ _ _ _ Htypt).
inversion H as [H1 [H2 H3]].
apply IHt1 in H1. apply IHt2 in H2. apply IHt3 in H3.
auto.
-
assert (Unit <: S)
by apply (typing_inversion_unit _ _ Htypt)...
Qed.
(型の)保存の証明は以前の章とほとんど同じです。
適切な場所で置換補題を使い、型付け仮定から構造的情報を抽出するために上述の反転補題をまた使います。
「定理」(保存): t、t'が項でTが型であり、empty |- t \in T かつ t --> t' ならば、empty |- t' \in T である。
「証明」:t と T が empty |- t \in T であるとする。
証明は、t'を特化しないまま型付け導出の構造に関する帰納法で進める。
(最後の規則が)T_Abs、T_Unit、T_True、T_Falseの場合は考えなくて良い。
なぜなら関数抽象と定数はステップを進めないからである。
T_Varも考えなくて良い。なぜならコンテキストが空だからである。
- もし導出の最後のステップの規則がT_Appならば、項t1 t2 と型 T1 T2 が存在して、t = t1 t2、T = T2、 empty |- t1 \in T1 -> T2、empty |- t2 \in T1 である。
- もし導出の最後のステップで使う規則がT_Subならば、型Sが存在して S <: T かつ empty |- t \in S となる。 型付け導出についての帰納法の仮定とT_Subの適用から結果がすぐに得られる。 ☐
Theorem preservation : forall t t' T,
empty |- t \in T ->
t --> t' ->
empty |- t' \in T.
Proof with eauto.
intros t t' T HT.
remember empty as Gamma. generalize dependent HeqGamma.
generalize dependent t'.
induction HT;
intros t' HeqGamma HE; subst; inversion HE; subst...
-
inversion HE; subst...
+
destruct (abs_arrow _ _ _ _ _ HT1) as [HA1 HA2].
apply substitution_preserves_typing with T...
Qed.
Records, via Products and Top
練習問題: ★★, standard (variations)
- 次の型付け規則を追加する:
Gamma |- t \in S1->S2 S1 <: T1 T1 <: S1 S2 <: T2 ----------------------------------- (T_Funny1) Gamma |- t \in T1->T2
- 次の簡約規則を追加する:
-------------------- (ST_Funny21) unit --> (\x:Top. x)
- 次のサブタイプ規則を追加する:
---------------- (S_Funny3) Unit <: Top->Top
- 次のサブタイプ規則を追加する:
---------------- (S_Funny4) Top->Top <: Unit
- 次の評価規則を追加する:
--------------------- (ST_Funny5) (unit t) --> (t unit)
- 上と同じ評価規則と新たな型付け規則を追加する:
--------------------- (ST_Funny5) (unit t) --> (t unit) -------------------------- (T_Funny6) empty |- unit \in Top->Top
- 関数型のサブタイプ規則を次のものに変更する:
S1 <: T1 S2 <: T2 ----------------- (S_Arrow') S1->S2 <: T1->T2
☐
☐