前稿 ジェネリクスのカリー化 - プログラマーの脳みそ では、Javaの内部クラスを利用して複数の型変数をもつジェネリックなクラスをカリー化することができるという話をした。
では以前のJavaによる高階型変数の実装 - プログラマーの脳みそで用いた手法はカリー化だったのかというとそれだけでは済まない。
なので高階型変数の実現に用いられているプログラミングパラダイムを分析しようというのが夏休みの宿題なのである…。
表現できないこと
ジェネリクスではそもそも何が表現できるのだっただろうか。
ジェネリックメソッドでは
- メソッドの引数間
- メソッドの戻り値
で型の関連性を示すのでした。インスタンスの型変数は更に
- 複数のメソッド間
- 公開されているフィールド
- 内部クラス
といったものの間で型の関連性を示すことになります
ジェネリックな設計 基礎編 - プログラマーの脳みそ
なにかしらの境界をおいて、境界をまたぐ部分での型の対応付けを抽象的に行うことができるのだった。例えばメソッドという境界で引数と戻り値の型の関係を表現するとか(ジェネリックメソッド)、インスタンスという境界でフィールドに用いられる型やメソッドに用いられる型の関連性を表現するとか(ジェネリックなクラス)だった。
ところが、Javaによる高階型変数の実装 - プログラマーの脳みそで出てきたのは
public class KeyValue<K> { public <T> void put(K<T> key, T value){} }
ということがしたいというモノだった。(上記コードはコンパイルできない)
ジェネリクスのカリー化 - プログラマーの脳みそでは内部クラスを型コンストラクタに見立てて用いることができることを検討したが、上記コードのような型の関連性を直接表現できない。
public class KeyValue<KG extends KeyGroup> { public <T> void put(KG.Key<T> key, T value){} } public class KeyGroup { private static final KeyGroup singleton = new KeyGroup(); public class Key<T> {} public static final Key<String> KEY_1 = singleton.new Key<String>(); }
ここでKeyGroupが外部クラスでKeyはその内部クラスなのであるが、KGにはextends KeyGroupという境界がついているものの、型変数KGにドットをつけて内部クラスをKG.Keyとは表現できないのだった。
つまり、冒頭挙げた
public class KeyValue<K> { public <T> void put(K<T> key, T value){} }
というコードではTというジェネリックメソッドの型変数を用いて引数の型の関連性を表現しているが、ここに型コンストラクタをKという型変数にしてしまった時点で関連性が表現できなくなってしまう。
というか、要するにJavaで内部クラスを持つ外部クラスを型変数にしちゃうと、そこから内部クラスを使う部分がいろいろとどうしようもなくなるという事なのである…。
苦肉の策で書かれたコードが以下のものだった。
public class KeyValue<KG extends KeyGroup<KG>> { public <T> void put(KeyGroup<KG>.Key<T> key, T value){} }
ここではKG.Keyという表現が使えないので再帰ジェネリクスを使った上でKGとKeyGroup
外部クラスと型変数
…この流れだと外部クラスを型変数で扱うとどうなるのかを書かないといけないところなのだが、疲れたので次回送りとしたい。