Scala 3でParameter Untuplingという機能が追加された。この機能は従来Scala 2で問題となっていたタプルまわりの取り回しを大きく改善する。
Parameter Untupling
従来、F[(A, B)]
のような型(例えばSeq[(String, Int)]
)に対してmap
を行うときは、case
を使って一旦値を分解する必要があった:
val xs = Seq( "foo" -> 42, "bar" -> 43, "buzz" -> 44, ) val ys: Seq[Boolean] = xs.map { case (k, v) => ... } xs.map { (k, v) => ... } // does not compile xs.map { pair => ... } // pair is (String, Int)
つまり、関数の中にはタプルとしての値しか渡ってこないのでその中の情報を知るには一度分解を挟まなければならなかった。
Scala 3からは、直接(k, v)
のように引数を受け取ることができるようになる:
val xs = Seq( "foo" -> 42, "bar" -> 43, "buzz" -> 44, ) val ys: Seq[Boolean] = xs.map { (k, v) => ... } // ok! val zs = Seq(42 -> 1, 43 -> 2) zs map { _ + _ } // このような使い方も可能
Scala 3からは、((T_1, ..., T_n)) => U
という型が要求されている局面に、2つ以上の引数を持つ関数を渡すと、その形に変換されるようになる。これがParameter Untuplingという機能である。
上掲の例では、要求される型は((String, Int)) => Boolean
である。実際に渡している箇所の型は(String, Int) => Boolean
だが、Parameter Untuplingによって自動的に((String, Int)) => Boolean
に変換される。この変換は自動的なので、map
の中では自由に(k, v)
としてタプルの要素を取り出せる。これはすごく便利だ。
この手法はメソッドでも利用可能だ:
def is42(key: String, value: Int): Boolean = value == 42 val xs = Seq( "foo" -> 42, "bar" -> 43, "buzz" -> 44, ) xs.map(is42)
注意しなければならないのが、Parameter Untuplingはメソッドのパラメータリストに対して適用されるという点だ。既に型が確定したただの関数についてこの変換は行なわれない:
val is42a: (String, Int) => Boolean = (_, v) => v == 42 val xs = Seq( "foo" -> 42, "bar" -> 43, "buzz" -> 44, ) xs.map(is42a) // does not compile xs.map(is42a.tupled) // tupledを使うと動く
おそらくScala開発者としても「型の暗黙変換」にまで踏み込むのはやりすぎと判断したのだろう。個人的にもちょうど良いバランスだと思う。