Functional Programming in ScalaのWikiを翻訳している中で path-dependent types という単語が出てきてよくわからなかったので調べました。 調べたらhishidamaさんのページが出てきてくれたので良かった(?)です。 詳細はそちらを参照していただければという感じなんですが、以下一応自分なりに噛み砕いてまとめてみました。
経路依存型(path-dependent types)
普通にJavaで内部クラスを定義したとすると、以下のような感じになると思います。Scalaでも同様に書けると思います。
class A {
class B {
}
}
さてここから先、内部クラスのインスタンスを生成したときの挙動がJavaとScalaで変わるそうです。 まずはJavaの例を見てみましょう。
Javaでは class A の異なるインスタンスa1, a2を用いて、それぞれの内部クラス B のインスタンスを生成したとしても、それらは同じ型です。 つまり以下のようなことができるはずです。
A a1 = new A();
A a2 = new A();
A.B b1 = a1.new B();
A.B b2 = a2.new B();
// b1 と b2 は同じ A.B型 なので、b1 に b2 を代入できる
b1 = b2;
しかしながらScalaで同様のことをやってみるとうまくいかないのです(やったことがなかったので気づかなかった!)。
val a1 = new A()
val a2 = new A()
var b1 = new a1.B() // 再代入するので var にしてある
val b2 = new a2.B()
b1 = b2 //=> 型があわないと怒られる
また生成元の class A が同じインスタンスであっても、生成経路が異なると違う型として扱われるらしいです(これは驚きました)。
val a1 = new A()
val a2 = a1
assert(a1 == a2) // a2 は a1 と同じインスタンス!
var b1 = new a1.B() // 再代入するので var にしてある
val b2 = new a2.B()
b1 = b2 //=> 型があわないと怒られる
したがって同じ経路で生成された内部クラスのインスタンス同士の型でないと同じ型として認識されません。 くどいようですが、コードで示すと以下のような具合になります。
val a = new A()
var b1 = new a.B() // 再代入するの var にしてある
val b2 = new a.B()
b1 = b2 //=> 代入できる!
なるほどなぁ…って感じです。ちなみにパス依存で異った型として扱われている内部クラスを同じクラスとして扱いたかったら、外側のクラス名#内部クラス名にキャストすれば良いようです。勉強になりました…。