Help us understand the problem. What is going on with this article?

第7章:タプルにチャレンジ!

More than 5 years have passed since last update.

第7章も相変わらず について語るぞ!
今回の主役は、Javaプログラマーには馴染みがない タプル

複数の戻り値

戻り値で複数の異なる型を返したくなったことはある?
Javaだと引数の型は1つと決まっているから、単純には実現できないよね。
だから、代替案としてこんな手を使ったりするよね。

  • 例えば、新たなDTO等の 情報格納役 を定義し、そのフィールドに複数の型を用意する
  • 引数に複数のオブジェクトを渡して、戻り値相当をそのオブジェクトに設定する
  • インタフェースを分割する。つまりそれぞれのデータを返すメソッドを用意する
  • 戻り値をコレクションにして、複数の型のオブジェクトを格納する(ほとんどやらないけど)

どれも面倒臭いんだよね。クラス名決めるのだって大変だし。。。

Javaの世界では諦めモードだけど、Scalaの世界では素晴らしい仕組みが備わっているんだ。
複数の型を返す仕組み、 タプル だ!

タプルの例

早速ソースを見てみよう。

Tuple.scala
object Tuple {

  def getMaxValue(numbers: List[Int]) = {
    val max = numbers.max
    val index = numbers.indexOf(max)
    (max, index)
  }

  def main(args: Array[String]){
    val numbers = List(1, 2, 3, 4, 5, 10, 6)

    val maxValue = getMaxValue(numbers)
    printf("max=%s \n", maxValue._1)
    printf("index=%s \n", maxValue._2)
  }

}

実行

実行すると以下のようになるよ。

$ scalac Tuple.scala

$ scala Tuple
max=10
index=5

ソースの説明

Tuple.scalaは単純なソースだ。
引数にIntListを受け取り、以下の戻り値を返すメソッドgetMaxValueを定義する。

  • 引数のList最大値
  • 引数のList最大値のインデックス

そして、その戻り値を確認する。

この説明だけで、メソッドgetMaxValue内でどれがタプルかわかるだろ?
(max, index)がタプルだ。

val max = numbers.max
val index = numbers.indexOf(max)

Listの最大値と最大値のインデックスはここで取得しているんだ。

val maxValue = getMaxValue(numbers)
printf("max=%s \n", maxValue._1)
printf("index=%s \n", maxValue._2)

タプルを変数maxValueに格納して、_1 _2でそれぞれの要素にアクセスしている。
格納した順番通りなので

  • _1が最大値
  • _2が最大値のインデックス

となる。

タプルとは

タプルとは、 複数の異なる型 を格納できるコンテナオブジェクトだ。
つまり 情報格納役 になる。

もちろんタプルにも型が存在している。
scala.TupleNとして、1~22のNに対してTupleNクラスが定義されている(ようだ)。
ということは、タプルには22個まで要素を格納できる。

また、それぞれの要素にアクセスするには_Nとする。

タプルの限界

22個なんて使うことはないと思いつつ、22個を超過したらどうなるか試してみる。

Tuple22

22個は最大値なので問題ないはず。

Tuple22.scala
object Tuple22 {

  def get() = {
    (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22)
  }

  def main(args: Array[String]){
    val tuple = get()
    printf("element_1=%s \n", tuple._1)
    printf("element_22=%s \n", tuple._22)
  }

}

22個の要素を持つタプルを用意して、1要素目と22要素目を取得する。

実行

$ scalac Tuple22.scala

$ scala Tuple22
element_1=1
element_22=22

これは期待通りの動きとなった。

Tuple23

23個の要素を持つタプルは、型が存在しないのでscalacでエラーになるはず。

Tuple23.scala
object Tuple23 {

  def get() = {
    (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
  }

  def main(args: Array[String]){
    val tuple = get()
    printf("element_1=%s \n", tuple._1)
    printf("element_23=%s \n", tuple._23)
  }

}

実行

scalacしてみると

$ scalac Tuple23.scala
Tuple23.scala:4: error: object Tuple23 is not a member of package scala
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
      ^
one error found

Tuple23なんていないぜって言っているから、予想通りだ。
23個以上は使うなよ!

Function22

Function22というのもあるんだ。
引数の数に応じて、Function1Function22まで存在している。
つまり23個以上は使えないってこと?

Func22.scala
object Func22 {
  def main(args: Array[String]) {
    val func22 : (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) => Int = null;
  }
}

実行

scalacしてみるよ!

scalac Func22.scala

コンパイルエラーはでないね。

Function23

次はFunction23を見てみよう。

Func23.scala
object Func23 {
  def main(args: Array[String]) {
    val func23 : (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) => Int = null;
  }
}

実行

scalacしてみるよ!

$ scalac Func23.scala
Func23.scala:3: error: type Function23 is not a member of package scala
    val func23 : (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) => Int = null;
                                                 ^
one error found

今度はコンパイルエラーになったね。Function23なんて型はないぜ!って言っている。
関数の引数は、やっぱり22個までということですね。

情報格納役オブジェクトで試す

Intの場合で試してみたので、今度は情報格納役オブジェクト
所謂JavaBean的なオブジェクトで試してみよう!

振る舞いも入れてみてるからな。

CaseClassTuple.scala
object CaseClassTuple {

  def get() = {
    val name = new Name("清美", "椿山")
    (Human(name, 15), name)
  }

  def main(args: Array[String]){
    val tuple = get()
    printf("fullname_1=%s \n", tuple._1.name.fullname)
    printf("fullname_2=%s \n", tuple._2.fullname)
  }

}

class Name(first: String, last: String) {
  def fullname() = first + " " + last
}

case class Human(name: Name, age: Int)

実行

実行してみる。

$ scalac CaseClassTuple.scala

$ scala CaseClassTuple
fullname_1=清美 椿山
fullname_2=清美 椿山

これもウマくいった。
ほぼほぼどんなオブジェクトでもいけそうな予感。

タプルもイミュータブル

もちろんというか、タプルもイミュータブルなので要素の入れ替えができないよ。
ちょっとチャレンジしてみました。

TupleImmutable.scala
object TupleImmutable {

  def get(x1: Int, x2: Int) = {
    (x1, x2)
  }

  def main(args: Array[String]){
    val tuple = get(1, 2)
    tuple._1 = 3
  }

}

実行

scalacしてみる。

$ scalac TupleImmutable.scala
TupleImmutable.scala:9: error: reassignment to val
tuple._1 = 3
         ^
one error found

イミュータブルだぜ!アサインできないぞ!と言っているね!

タプルのメリット

型の増大を防ぐことができるのがメリットだと思うよ。
特に、サービス層で使える気がしている。

異なる型のデータ取得のためだけに、サービスのインタフェースを複数切っていたところを、
Facadeにまとめてしまい、タプルでデータを返すってコトができると思う。

他にも使い道ありそうだね。

まとめ

今回はタプルについて語ってみたけどどうかな?
自分が疑問に思っていたところも試すことが出来たから、良かったぞ。

今回も
体で感じてくれたかな?

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away