読者です 読者をやめる 読者になる 読者になる

さんぽみち

なにか思いついたときとかに気まぐれに更新されます。

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パターンを詰めて考えた結果を書きたいと思います。