val t = (1, "Mike", 1000)
val name = t._2
println(name) // Mike
ここでの t._2
のような要素のアクセスは読みにくい.
この程度のコード量なら問題ないかもしれないが, タプルの種類が増えたり, 要素が増えると読むのが辛くなるのでどうにかしたい.
要約
パターンマッチを駆使しよう.
パターンマッチを使った代入で可読性を上げる
つまりこういうこと.
val t = (1, "Mike", 1000)
// val name = t._2
val (_, name, _) = t
println(name) // Mike
使わない部分はアンダースコアで捨てるとよい.
タプルのシーケンスの場合
素直に書くとこうなるだろう.
val data = Seq((1, "Mike", 1000), (2, "Harvey", 1500), (3, "Louis", 2000))
data.foreach(t => println(t._2)) // Mike Harvey Louis
これをパターンマッチを使って読みやすくしてみる.
val data = Seq((1, "Mike", 1000), (2, "Harvey", 1500), (3, "Louis", 2000))
data.foreach { case (_, name, _) => println(name) } // Mike Harvey Louis
Pattern Matching Anonymous Function というものらしい. (たぶん)
もちろん for-yield の中でも以下のようにできる.
val data = Seq((1, "Mike", 1000), (2, "Harvey", 1500), (3, "Louis", 2000))
val names = for { (_, name, _) <- data } yield name // names: Seq[String] = List(Mike, Harvey, Louis)
Slick での例
筆者は ORM の Slick を用いるときにこのテクニックをよく使う.
// Dramas, Episodes, Comments は slick-codegen で DB のスキーマから生成された TableQuery
val query = for {
((drama, episode), comment) <-
Dramas
.filter(_.title === "Suits")
.join(Episodes)
.join(Comments)
.on {
case ((drama, episode), comments) =>
drama.id === episode.dramaId && episode.id === comments.episodeId
}
.sortBy { case ((_, episode), _) => episode.season }
} yield (drama, episode, character)
例えばソートするところは
.sortBy(_._1._2.season)
と同じだが, どちらが読みやすいかは明白だろう.
補足
タプルが何かしらのまとまりならクラスを使う
そもそもそのタプルが「意味のあるかたまり」ならクラスでまとめておいた方がいい.
case class Character(id: Int, name: String, asset: Int)
val c = Character(1, "Mike", 1000)
println(c.name) // Mike
val data = Seq(Character(1, "Mike", 1000), Character(2, "Harvey", 1500), Character(3, "Louis", 2000))
data.foreach(c => println(c.name)) // Mike Harvey Louis