注釈
- メタ変数 A, B, C, D, E はクラス名
- f, g はフィールド名
- m はメソッド名
- x は仮引数名
- s, t は項
- u, v は値
★ 構文
- <f> は f1, ..., fn の略記
- <C f> は C1 f1, ... , Cn fn の略記
- <C f;> は C1 f1; ... Cn fn; の略記
- this.<f> = <f> は this.f1 = f1; ... this.fn = fn; の略記
- <M> は M1 ... Mn の略記
- 宣言された名前は、重複してはいけない
CL | ::= | class C extends C { <C f;> K <M> } | | クラス宣言 |
K | ::= | C(<C f>) { super(<f>); this.<f> = <f>; } | | コンストラクタ宣言 |
M | ::= | C m(<C x>) { return t; } | | メソッド宣言 |
t | ::= | x | 変数 | 項 |
| | t.f | フィールドアクセス | |
| | t.m(<t>) | メソッド呼び出し | |
| | new C(<t>) | オブジェクト生成 | |
| | (C) t | キャスト | |
| | | |
v | ::= | new C(<v>) | オブジェクト生成 | 値 |
★ 部分型付け
| | CT(C) = class C extends D { .. } | | C <: D |
|
※ コンストラクタ引数は、インスタンス変数とちょうど同じ数だけでなくてはならない。
※ 完全な Java では、スーパークラスのコンストラクタが引数を受け取る場合のみ、スーパークラスのコンストラクタの呼び出しが必須
クラス表 CT は、クラス名 C から クラス宣言 CL への写像である。
クラス表は、以下の健全性条件を満たすものと仮定する。
略全てのクラスは extends で宣言されたスーパークラスを持つ。
ここでは、Object を特別なクラスとして扱い、クラス表の中に定義が現れない特別なクラス名とする。
演習 19.4.1. S-Top 規則のように、Object がすべてのクラスの上位型であることを述べた規則はここでは必要ない。なぜだろうか。
解答)
クラス宣言で必ず extends D を書かなければいけないので、上位型をたどっていけば、いつかは Object で止まる。あとは部分型付けの推移律を使えよいかからら。
補助的な定義型付け規則と評価規則のために、補助的な定義をいくつか定める必要がある。
- クラス C のフィールドの列を fields(C) と書く。スーパークラスで宣言されているフィールドを含む。
- クラス C のメソッド m の型を、mtype(m, C) と書く。
- クラス C のメソッド m の本体を mbody(m, C) と書く。
- 述語 override(m, D, <C> → C0) は、引数の型が<C>で、結果の型が C0 であるメソッド m を D のサブクラスに定義可能かどうかを判断する。
★ フィールドの探索
| CT(C) = class C extends D {<C f;> K <M>} | fields(D) = <D g> | | fields(C) = <D g> + <C f> |
|
★ メソッドの型の探索
CT(C) = class C extends D { <C f;> K <M> } | B m (<B x>) { return t; } ∈ <M> | |
mtype(m, C) = <B> → B |
CT(C) = class C extends D { <C f;> K <M> } | m は <M> の中で定義されていない | |
mtype(m, C) = mtype(m, D) |
★ メソッド本体の探索
CT(C) = class C extends D { <C f;> K <M> } | B m (<B x>) { return t; } ∈ <M> | |
mybody(m, C) = (<x>, t) |
CT(C) = class C extends D { <C f;> K <M> } | m は <M> の中で定義されていない | |
mbody(m, C) = mbody(m, D) |
★ メソッドの正当なオーバーライド
mtype(m, D) = <D> → D0 ならば <C> = <D> かつ C0 = D0 | |
override(m, D, <C> → C0) |
評価標準的な値呼び操作的意味論を用いる。
評価器の正常終了の結果となりうる値は、new C(<v>) という形の、完全に引数の評価されたオブジェクト生成の項である。
fields(C) = <C f> | E-ProjNew | (new C(<v>)).fi → vi |
| mbody(m, C) = (<x>, t0) | InvkNew | (new C(<v>)).m(<u>) |
| C <: D | E-CaseNew | (D)(new (<b>)) → new C(<v>) |
|
t0 → t0' | E-Field |
t0.f → t0'.f |
t0 → t0' | E-InvkRecv |
t0.m(<t>) → t0'.m(<t>) |
ti → ti' | E-InvkArg |
v0.m(<v>, ti, <t>) → v0'.m(<v>, ti', <t>) |
ti → ti' | E-NewArg |
new C(<v>, ti, <t>) → new C(<v>, ti', <t>) |
t0 → t0' | E-Cast |
(C)t0 → (C)t0' |
型付け「愚かな」キャストの導入 (C) D
- アップキャスト:D <: C の場合 例:(Object) (new A())
- ダウンキャスト:C <: D の場合 例:(A) (new Object())
- 愚かなキャスト:D と C が無関係の場合
Java コンパイラは愚かなキャストを含む項を、正しく型付けされないもとして拒否する。
しかし FJ では、小ステップ意味論の型保存定理として型安全性を定式化する以上、愚かなキャストを認める必要がある。これは、愚かなキャストを含まない項が愚かなキャストを含む項に簡約されることがあるからである。
(A)(Object)new B() → (A)new B()
このため、前提部に愚かなキャストの性質を含めた規則 T-SCast を定義している。
FJ の型付けが正しい Java の型付けに対応するのは、この規則を除いたときのみである。
M OK in C という形式は、「メソッド宣言 M は、それが C の中にあるなら正しい」と読む。メソッド本体の中に現れ売る自由変数は、メソッドの仮引数と this のみである。
CL OK という形式は、「クラス宣言 CL は正しい形式をしている」と読む。
Γ├ t0 : C0 | fields(C0) = <C f> | T-Field |
Γ├ t0.fi : Ci |
Γ├ t0 : C0 | mtype(m, C0) = <D> → C | Γ├ <t> : <C> | <C> <: <D> | T-Invk |
Γ├ t0.m(<t>) : C |
Γ├ <t> : <C> | <C> <: <D> | T-New |
Γ├ new C(<t>) : C |
Γ├ t0 : D | D <: C | T-UCast |
Γ├ (C)t0 : C |
Γ├ t0 : D | C <: D | C != D | T-DCase |
Γ├ (C)t0 : C |
Γ├ t0 : D | C !<: D | D !<: C | 愚かさの警告 | T-SCast |
Γ├ (C)t0 : C |
<x> : <C>, this : C ├ t0 : E0 | E0 <: C0 | CT(C) = class C extends D { ... } | override(m, D, <C> → C0) | T-Method |
C0 m (<C x>) { return t0; } OK in C |
K = C(<D g>, <C f>) | { super(<g>); this.<f> = <f>; } | fields(D) = <D g> | <M> OK in C | T-Class |
class C extends D { <C f;> K <M> } OK |
演習 19.4.2. FJ は Java のサブセットであるので、FJ プログラムは正しく型付けされているときのみ 正しく型付けされた Java プログラムになることが要求されている。
仮にこの要求を捨てて、単に Java 風の核となる計算体系が欲しいとき、設計をどのように考えるだろうか。
解答)
キャストの三つの型付け規則を1つにまとめて、愚かなキャストの概念を捨てるなど。
Γ├ t0 : D | T-Cast |
Γ├ (C)t0 : C |
演習 19.4.3. 破壊的代入は計算体系の基本を大きく変えることなく追加せよ。13章の参照の扱い方を参考にするとよい。
解答)
省略
演習 19.4.4. 14章の例外の扱い方を参考にして、Java の throw, try のようなもので FJ を拡張せよ。
解答)
省略
演習 19.4.5. 完全な Java と同様、FJ では型付け関係をアルゴリズム的な形式で表現していて、包摂関係はない。今ある規則の代わりに、包摂規則を導入して型システムをより宣言的に形式化しなおせるだろうか。
Γ├ t : S | S <: T | T-SUB |
Γ├ t : T |
解答)
例えば、Γ├ new A() : A, A <: Object が成り立つとき、T-SUB 規則を導入すると Γ├ new A() : Object が成り立つが、これは今の規則体系と相容れない。代わりに、規則 T-UCast より Γ├ (Object)new A() : Object のようにキャスト表現をつけたものが成り立つ。
包括規則を入れて宣言的な定義に形式化しなおすと、健全性が保てなくなるのでは?
演習 19.4.6. Java のインターフェースはメソッドの型を指定するが、実装は持たない。また、クラス継承と違い、任意の数のインターフェースを実装することができる。
型付け可能な項に対して最小の型を与えてくれる、
インターフェースと条件式(Java では t1 ? t2 : t3 と書く)の間の相互作用のため、型付け可能な項に対して最小の型を与えるような型付け関係のアルゴリズムが必要である。
(1) Java 流のインターフェースで FJ を拡張する方法を示せ
(2) インターフェースが存在するなら、部分型関係が結びの下で必ずしも閉じるとは限らないことを示せ。(結びの存在は、条件式の最小型付け性に重要な役割を果たす)
(3) 条件式に対する Java の型付け規則はどのようなものだろうか。それは妥当か。
解答)
(1)
クラス宣言に加えて、インターフェース宣言を追加する。
(2) 以下のようなインターフェースを宣言したとする
- interface A {}
- interface B {}
- interface C extends A, B {}
- interface D extends A, B {}
(3)
条件式に対する標準的なアルゴリズム的規則は以下であったが、
Γ├ t1 : boolean | Γ├ t2 : E2 | Γ├ t3 : E3 | |
Γ├ (t1 ? t2 : t3) : E2 v E3 |
これの代わりに、以下のように制限した規則を用いる。
... | Γ├ E2 <: E3 | |
Γ├ ... : E2 |
... | Γ├ E3 <: E2 | |
Γ├ ... : E3 |
演習 19.4.7. FJ に super のキーワードを追加する方法を示せ。
解答)
これを実現するには、「現在実行中のメソッド本体」が属するクラスを覚えておかねばならない。
(1) どこを参照すれば良いかについて、項に何らかの注釈を追加する。
(2) super の参照を this の参照と同じにしておき、現在実行中のメソッドがどのクラスのメソッドなのかという情報を、呼び出したい別のメソッドに埋め込む。