Java Tupleの実装
はじめに
Javaには二つの型を格納できる Tuple しか標準ライブラリには存在しないようです。
不便なので動きやすくするためにとりあえず10個の型引数までに対応した Tuple を作ります。
実装方法
実装方法について、
1. 型引数の数に対して1つずつクラスを定義していく
2. リスト構造をとり、クラスそのものは一つの定義とする
の二通りが考えられましたが、今回は1番の方法で実装することにします。
今回は10個の型に対応した Tuple までを実装しますが、人力で実装していては変なバグを作りこむ可能性があります。
そして何よりめんどくさいです。
幸いにもこれらのソースは構造を持った文字列になる為、プログラムを書くプログラムを実装します。
今回はさくっと書ける Ruby で実装しました。
また、2番の方法ですでに実装した先人がおられるのでリンクを張っておきます。
qiita.com
ソース
はじめにソースを生成するRubyのソースを載せます。
TupleMaker.rb
#tuple maker Package = "tuples" Tuples = ["Single", "Pair", "Triple", "Quadruple", "Quintuple", "Sextuple", "Septuple", "Octuple", "Nonuple", "Decuple"] def makePackage(package) "package " + package + ";\n" end def makeImport(import) "import " + import + ";\n" end def makeFields(len) ret = "" len.times{|i| ret += " private final I" + (i + 1).to_s + " _item" + (i + 1).to_s + ";\n" } ret end def makeTypeArgs(len) ret = "<" (len - 1).times{|i| ret += "I" + (i + 1).to_s + ", " } ret += "I" + len.to_s + ">" end def makeArgs(len, hasType) ret = "(" (len - 1).times{|i| ret += (hasType ? "I" + (i + 1).to_s + " ": "") + "item" + (i + 1).to_s + ", " } ret += (hasType ? "I" + len.to_s + " ": "") + "item" + len.to_s + ")" end def makeCreate(className, len) " public static " + makeTypeArgs(len) + " " + className + makeTypeArgs(len) + " create" + makeArgs(len, true) + "\n" + " {\n" + " return new " + className + makeTypeArgs(len) + makeArgs(len, false) + ";\n" + " }\n" end def makeConstractor(className, len) ret = " private " + className + makeArgs(len, true) + "\n" ret += " {\n" (len - 1).times{|i| ret += " this._item" + (i + 1).to_s + " = item" + (i + 1).to_s + ";\n" } ret += " this._item" + len.to_s + " = item" + len.to_s + ";\n" ret += " }\n" end def makeGetter(len) ret = "" len.times{|idx| i = idx + 1 ret += " public I" + i.to_s + " item" + i.to_s + "()\n" ret += " { return this._item" + i.to_s + "; }\n" } ret end def makeCurry(len, index, last) index >= len ? last: # otherwise "Function<I" + (index + 1).to_s + ", " + makeCurry(len, index + 1, last) + ">" end def makeUseCurry(len, index, last) index >= len ? last: # otherwise ".apply(this._item" + (index + 1).to_s + ")" + makeUseCurry(len, index + 1, last) end def makeAccept(className, len) " public " + className + makeTypeArgs(len) + " accept" + "(" + makeCurry(len - 1, 0, "Consumer<I" + len.to_s + ">") + " f)\n" + " {\n" + " f" + makeUseCurry(len - 1, 0, ".accept(this._item" + len.to_s + ")") + ";\n" + " return this;\n" + " }\n" end def makeApply(len) " public <R> R apply(" + makeCurry(len - 1, 0, "Function<I" + len.to_s + ", R>") + " f)\n" + " {\n" + " return f" + makeUseCurry(len - 1, 0, ".apply(this._item" + len.to_s + ")") + ";\n" + " }\n" end def makeClass(packageName, className, len) makePackage(packageName) + "\n" + makeImport("java.util.function.*") + "\n" + "public class " + className + makeTypeArgs(len) + "\n" + "{\n" + makeFields(len) + "\n" + makeCreate(className, len) + "\n" + makeConstractor(className, len) + "\n" + makeGetter(len) + "\n" + makeAccept(className, len) + makeApply(len) + "}\n" end def makeTupleClass(tuples, package) ret = makePackage(package) + "\n" + "public final class Tuple\n" + "{\n" + " private Tuple()\n" + " { }\n" tuples.each_with_index{|tp, idx| i = idx + 1 ret += "\n" ret += " public static " + makeTypeArgs(i) + " " + tp + makeTypeArgs(i) + " create" + makeArgs(i, true) + "\n" ret += " { return " + tp + ".create" + makeArgs(i, false) + "; }\n" open(tp + ".java", "w"){|f| f.puts makeClass(package, tp, i) } } ret += "}\n" open("Tuple.java", "w"){|f| f.puts ret } end makeTupleClass(Tuples, Package)
使い捨てのソースなんてきっとみんなこんなものです、汚かったり効率が悪いのは目をつむってください。
実行前後のディレクトリはこのようになります。
X:\tuples>dir ドライブ X のボリューム ラベルは Windows です ボリューム シリアル番号は ****-**** です X:\tuples のディレクトリ 2016/07/27 21:26 <DIR> . 2016/07/27 21:26 <DIR> .. 2016/07/27 21:18 3,650 TupleMaker.rb 1 個のファイル 3,650 バイト 2 個のディレクトリ **,***,***,*** バイトの空き領域 X:\tuples>ruby TupleMaker.rb X:\tuples>dir ドライブ X のボリューム ラベルは Windows です ボリューム シリアル番号は ****-**** です X:\tuples のディレクトリ 2016/07/27 21:22 <DIR> . 2016/07/27 21:22 <DIR> .. 2016/07/27 21:18 2,611 Decuple.java 2016/07/27 21:18 2,365 Nonuple.java 2016/07/27 21:18 2,140 Octuple.java 2016/07/27 21:18 775 Pair.java 2016/07/27 21:18 1,250 Quadruple.java 2016/07/27 21:18 1,475 Quintuple.java 2016/07/27 21:18 1,920 Septuple.java 2016/07/27 21:18 1,695 Sextuple.java 2016/07/27 21:18 560 Single.java 2016/07/27 21:18 1,010 Triple.java 2016/07/27 21:18 2,120 Tuple.java 2016/07/27 21:18 3,650 TupleMaker.rb 12 個のファイル 21,571 バイト 2 個のディレクトリ **,***,***,*** バイトの空き領域
次に、生成したソースを載せます。
Tupleの具体的な定義は種類が多い為、3個の型を扱う "Tripleクラス" と 10個の型を扱う "Decupleクラス" の二つを載せます。
Tuple.java
package tuples; public final class Tuple { private Tuple() { } public static <I1> Single<I1> create(I1 item1) { return Single.create(item1); } public static <I1, I2> Pair<I1, I2> create(I1 item1, I2 item2) { return Pair.create(item1, item2); } public static <I1, I2, I3> Triple<I1, I2, I3> create(I1 item1, I2 item2, I3 item3) { return Triple.create(item1, item2, item3); } public static <I1, I2, I3, I4> Quadruple<I1, I2, I3, I4> create(I1 item1, I2 item2, I3 item3, I4 item4) { return Quadruple.create(item1, item2, item3, item4); } public static <I1, I2, I3, I4, I5> Quintuple<I1, I2, I3, I4, I5> create(I1 item1, I2 item2, I3 item3, I4 item4, I5 item5) { return Quintuple.create(item1, item2, item3, item4, item5); } public static <I1, I2, I3, I4, I5, I6> Sextuple<I1, I2, I3, I4, I5, I6> create(I1 item1, I2 item2, I3 item3, I4 item4, I5 item5, I6 item6) { return Sextuple.create(item1, item2, item3, item4, item5, item6); } public static <I1, I2, I3, I4, I5, I6, I7> Septuple<I1, I2, I3, I4, I5, I6, I7> create(I1 item1, I2 item2, I3 item3, I4 item4, I5 item5, I6 item6, I7 item7) { return Septuple.create(item1, item2, item3, item4, item5, item6, item7); } public static <I1, I2, I3, I4, I5, I6, I7, I8> Octuple<I1, I2, I3, I4, I5, I6, I7, I8> create(I1 item1, I2 item2, I3 item3, I4 item4, I5 item5, I6 item6, I7 item7, I8 item8) { return Octuple.create(item1, item2, item3, item4, item5, item6, item7, item8); } public static <I1, I2, I3, I4, I5, I6, I7, I8, I9> Nonuple<I1, I2, I3, I4, I5, I6, I7, I8, I9> create(I1 item1, I2 item2, I3 item3, I4 item4, I5 item5, I6 item6, I7 item7, I8 item8, I9 item9) { return Nonuple.create(item1, item2, item3, item4, item5, item6, item7, item8, item9); } public static <I1, I2, I3, I4, I5, I6, I7, I8, I9, I10> Decuple<I1, I2, I3, I4, I5, I6, I7, I8, I9, I10> create(I1 item1, I2 item2, I3 item3, I4 item4, I5 item5, I6 item6, I7 item7, I8 item8, I9 item9, I10 item10) { return Decuple.create(item1, item2, item3, item4, item5, item6, item7, item8, item9, item10); } }
Triple.java
package tuples; import java.util.function.*; public class Triple<I1, I2, I3> { private final I1 _item1; private final I2 _item2; private final I3 _item3; public static <I1, I2, I3> Triple<I1, I2, I3> create(I1 item1, I2 item2, I3 item3) { return new Triple<I1, I2, I3>(item1, item2, item3); } private Triple(I1 item1, I2 item2, I3 item3) { this._item1 = item1; this._item2 = item2; this._item3 = item3; } public I1 item1() { return this._item1; } public I2 item2() { return this._item2; } public I3 item3() { return this._item3; } public Triple<I1, I2, I3> accept(Function<I1, Function<I2, Consumer<I3>>> f) { f.apply(this._item1).apply(this._item2).accept(this._item3); return this; } public <R> R apply(Function<I1, Function<I2, Function<I3, R>>> f) { return f.apply(this._item1).apply(this._item2).apply(this._item3); } }
Decuple.java
package tuples; import java.util.function.*; public class Decuple<I1, I2, I3, I4, I5, I6, I7, I8, I9, I10> { private final I1 _item1; private final I2 _item2; private final I3 _item3; private final I4 _item4; private final I5 _item5; private final I6 _item6; private final I7 _item7; private final I8 _item8; private final I9 _item9; private final I10 _item10; public static <I1, I2, I3, I4, I5, I6, I7, I8, I9, I10> Decuple<I1, I2, I3, I4, I5, I6, I7, I8, I9, I10> create(I1 item1, I2 item2, I3 item3, I4 item4, I5 item5, I6 item6, I7 item7, I8 item8, I9 item9, I10 item10) { return new Decuple<I1, I2, I3, I4, I5, I6, I7, I8, I9, I10>(item1, item2, item3, item4, item5, item6, item7, item8, item9, item10); } private Decuple(I1 item1, I2 item2, I3 item3, I4 item4, I5 item5, I6 item6, I7 item7, I8 item8, I9 item9, I10 item10) { this._item1 = item1; this._item2 = item2; this._item3 = item3; this._item4 = item4; this._item5 = item5; this._item6 = item6; this._item7 = item7; this._item8 = item8; this._item9 = item9; this._item10 = item10; } public I1 item1() { return this._item1; } public I2 item2() { return this._item2; } public I3 item3() { return this._item3; } public I4 item4() { return this._item4; } public I5 item5() { return this._item5; } public I6 item6() { return this._item6; } public I7 item7() { return this._item7; } public I8 item8() { return this._item8; } public I9 item9() { return this._item9; } public I10 item10() { return this._item10; } public Decuple<I1, I2, I3, I4, I5, I6, I7, I8, I9, I10> accept(Function<I1, Function<I2, Function<I3, Function<I4, Function<I5, Function<I6, Function<I7, Function<I8, Function<I9, Consumer<I10>>>>>>>>>> f) { f.apply(this._item1).apply(this._item2).apply(this._item3).apply(this._item4).apply(this._item5).apply(this._item6).apply(this._item7).apply(this._item8).apply(this._item9).accept(this._item10); return this; } public <R> R apply(Function<I1, Function<I2, Function<I3, Function<I4, Function<I5, Function<I6, Function<I7, Function<I8, Function<I9, Function<I10, R>>>>>>>>>> f) { return f.apply(this._item1).apply(this._item2).apply(this._item3).apply(this._item4).apply(this._item5).apply(this._item6).apply(this._item7).apply(this._item8).apply(this._item9).apply(this._item10); } }
10個の型引数とこれのカリー化された式にもなると、人の手で記述したときに絶対と断言していいほどミスをしそうですね。
Tupleクラスは、 createメソッド をオーバーロードすることで利用者に対して汎用型そのものの違いを意識させず、型推論させることで面倒な型引数の記述を省きます。
acceptメソッド と applyメソッド を用意したいが為に、先ほど示した2種類の実装方法から1番を選択しました。
このメソッドを用意しておくことで、
String test = Tuple .create(1, "に", true) .apply( num -> str -> flag -> Tuple.create(str + num.toString(), flag) ) .apply( str -> flag -> flag ? str + str : str );
のような形で、わざわざそれぞれにアクセスせず展開できます。
haskellなどの言語で、パターンマッチングによりタプルから要素を取り出すのに似ていますね。
t = (1, "two", True) (num, str, flag) = t str' = str ++ show(num) str'' | flag = str' ++ str' | otherwise = str'
また、メソッドチェーンを繰り返すことで利用中に Pair と Triple というクラス名が一度も出てこず、引数をラムダ式で記述することにより扱っている値の型も明示が不要となります。
おわりに
Tupleは一度使うと中毒性があります。
シチュエーションとしては、メソッドチェーン中に複数の値を次のチェーンへ渡したい場合などが多いと思います。
Javaではないと聞いてがっかりしそうになりました。
でも、限定的ではあるものの型推論の機能が備わっている為、むしろ限定的であることが作用していかに推論させるかを考えた結果として使い勝手もそれなりのものができたと思います。
次はJavaっぽく、Decoratorパターンを詰めて考えた結果を書きたいと思います。