Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

Scala 3から導入されたParameter Untuplingが便利

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開発者としても「型の暗黙変換」にまで踏み込むのはやりすぎと判断したのだろう。個人的にもちょうど良いバランスだと思う。

参考文献

docs.scala-lang.org

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?