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

きくらげ観察日記

好きなことを、適当に。

独自パターンマッチを定義する。

Scala

Scalaでは、unapply, unapplySeqメソッドを定義することによって、任意の型に対して独自で後付けのパターンマッチを定義することができます。

unapply

何らかの値Oに対して、O.unapply(x)はパターンマッチに成功したら Some( (x1, x2, x3, ..., xN) ) を返し、x match { case O(x1, x2, ..., xN) => ... } の形で受け取ることができるようになります。パターンマッチに失敗した場合はNoneを返します。このようなunapplyを定義することによって、独自のパターンマッチを追加することができます。

例えば、ペアをk -> vの形で定義できるのだからパターンマッチもk -> vの形で行いたい、という場合。

object -> {
  def unapply[T, U](p: (T, U)): Option[(T, U)] = Some(p)
}

このオブジェクト -> を定義すると、ペア(x, y)をcase ->(x, y)の形でパターンマッチすることができます。
このことと、パターンP(x, y)を(x P y)と書けるという構文により、ペアに対する(k -> v)の形でのパターンマッチを行うことができます。

実行例

scala> val m = Map("pizza" -> 2000, "ramen" -> 850, "hamburger" -> 550)
scala> m.foreach { case (food -> price) => println(food) }
pizza
ramen
hamburger

unapplySeq

パターン P(x0, x1, ...)の括弧の中の要素数が不定の場合は、unapplySeqを使います。例えば、文字列を単語毎に区切ってWords(w1, w2, ...)の形で受け取りたい場合、

object Words {
  def unapplySeq(x: String): Option[List[String]] = Some(x.split(raw"\s+").toList)
}

とすることによって、以下のように文字列を単語毎に受け取れるようになります。

実行例

scala> "this is a pen" match { case Words(head, rest@_*) => head }
res1: String = this

scala> "this is a pen" match { case Words("this", "is", "a", what) => what }
res2: String = pen


もちろん、unapplyやunapplySeqを定義できるのはobject内だけではありません。例えば、scala.util.matching.Regexでは、正規表現内のグルーピングした部分に対してパターンマッチを行うことができるようなunapplyが定義されています。

scala> val mail = raw"([a-zA-Z0-9-]+)@([a-zA-Z0-9.]+)".r // メールアドレス(あまり正確ではない)
mail: scala.util.matching.Regex = ([a-zA-Z0-9-]+)@([a-zA-Z0-9.]+)

scala> "the-name@example.com" match { case mail(user, host) => println(f"user: ${user}%s, host: ${host}%s") }
user: the-name, host: example.com


Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第2版

Scala逆引きレシピ

Scala逆引きレシピ