JavaでTupleってみる
可変長な型変数の表現より、タプルの話です。
.NETのタプル
.NETでは、型引数の数が違うクラスを定義できるので、Tuple
という名前で7要素まで対応しています。
public class Tuple<T1> { ... } public class Tuple<T1, T2> { ... } public class Tuple<T1, T2, T3> { ... } public class Tuple<T1, T2, T3, T4> { ... } public class Tuple<T1, T2, T3, T4, T5> { ... } public class Tuple<T1, T2, T3, T4, T5, T6> { ... } public class Tuple<T1, T2, T3, T4, T5, T6, T7> { ... }
そして、各要素にアクセスするために、Item1
, Item2
のようなプロパティを使います。
さて、では.NETのタプルは8要素以上は対応していないかと言うと、そうではありません。
タプルのインスタンスを作るためには、Tuple.Create
というメソッドを使って作るのが楽です。
そしてこのTuple.Create
は、なんと8引数版まで用意されているのです。
var t = Tuple.Create(true, 2, 3.0, "4", new[] { 5 }, 6M, 7L, 8);
このt
の型は、こうなります。
Tuple<bool, int, double, string, int[], decimal, long, Tuple<int>>
実は、Tuple
は8番目の型引数として、「残りの要素を保持するタプルの型」が受け取れるようになっています。
ちなみにTuple<T1>
という謎のクラスはこの時のみ使われるクラスです。
public class Tuple<T1, T2, T3, T4, T5, T6, T7, TRest> { ... }
「残りの要素を保持するタプル」にアクセスするためには、Item8
ではなく、Rest
という名前のプロパティを使います。
そのため、8要素タプルの8番目の要素にアクセスしたい場合は、
var value = t.Rest.Item1;
となります。 さてさて、ここからが.NETの残念なところなのですが、実は9要素以上のタプルを作る簡単な方法は用意されていません*1。 ので、コンストラクタを使うことになります。
var t = new Tuple<int, int, int, int, int, int, int, Tuple<int, int>>( 1, 2, 3, 4, 5, 6, 7, Tuple.Create(8, 9));
コンストラクタの型パラメータは省略できないので、悲惨なことになっていますね・・・
これは、Tuple.Create
の8要素版が8要素タプルを作るためではなく、ネストしたタプルを作るためのヘルパーになっていればもっと楽が出来たのです。
var t = Tuple.Create(1, 2, 3, 4, 5, 6, 7, Tuple.Create(8, 9)); // こうはなっていない
残念すぎる・・・
Restという考え方
さて、.NETのタプルは残念ですが、「足りない部分はRestで」というのはどこかで聞いたことのあるような話です。 そう、コンスセルですね!
ということで、コンスセルの考え方を使ってタプルのないJavaにタプルを作ってみましょう。 まずは、コンスセルの終端を表すためのクラスを導入します。 以降では、特にパッケージとか書きませんけど、全部同一パッケージに入っていると思ってください。
public final class TupleNil { static final TupleNil nil = new TupleNil(); private TupleNil() {} }
簡単ですね。
こいつは状態を持っておらず、インスタンスも外部からは生成できず、
さらには唯一の静的フィールドであるnil
もパッケージプライベートなので、
パッケージ外からはこのフィールドにすらアクセスできません。
これだけでは本当に全く何の役にも立たないクラスです。
次に、2つの型パラメータを取って実際に要素を保持するクラスを作ります。
public final class TupleCons<T, Rest> { public final T value; public final Rest rest; TupleCons(T value, Rest rest) { this.value = value; this.rest = rest; } }
これも簡単ですね。
単に、値を2つ持っているだけのクラスです。
このクラスのvalue
に値が、rest
に残りの要素を表すものが格納されます。
コンストラクタがパッケージプライベートなので、パッケージ外からこのクラスのインスタンスを生成することはできません。
最後にタプルを作るためのメソッドを持ったクラスです。
public final class Tuple { private Tuple() {} public static <T> TupleCons<T, TupleNil> singleton(T value) { // こことか return new TupleCons<T, TupleNil>(value, TupleNil.nil); } public static <T1, T2, Rest> TupleCons<T1, TupleCons<T2, Rest>> cons(T1 value, TupleCons<T2, TupleNil> rest) { // ここってダイアモンド演算子使えるんですか?使えそうだけどJavaとかわかりません>< return new TupleCons<T1, TupleCons<T2, Rest>>(value, rest); } }
あとは、これを使ったコードです。
TupleCons<Integer, TupleCons<String, TupleNil>> t2 = Tuple.cons(42, Tuple.singleton("hoge")); System.out.println(t2.value); // 42 System.out.println(t2.rest.value); // hoge
やったー!(何が
まとめ(ない)
- .NETのタプルは割と現実見て、7要素までは自然に扱える
- 大量要素のタプル使うな
- でももし扱う場合があるといけないから、一応対応しとくぜ!
- .NETの
Tuple.Create
はクソ - タプルってコンパイル時のリスト(コンスセル)だよね!
*1:まぁ、そんなタプル作るなと言われればその通り、としか言えないですが