【match】Scala のパターンマッチ

参考にした本は Scalaスケーラブルプログラミング 第3版 第15章 ケースクラスとパターンマッチ です。

環境は macOS 10.12.3, Scala 2.12 です。

パターンマッチ

match 式は `selector match { case pattern => expr }` のように書き, パターンが match するとその後ろの式が評価される。

主なパターンマッチの種類には以下がある。

  • ワイルドカードパターン
  • 定数パターン
  • 変数パターン
  • コンストラクタパターン
  • シーケンスパターン
  • タプルパターン
  • 型付きパターン

ワイルドカードパターン

ワイルドカードパターン(_) は全てのオブジェクトにマッチする。

scala> def isString(x: Any) = x match {
     |   case s: String => true
     |   case _ => false
     | }
isString: (x: Any)Boolean

scala> isString(Seq(1,2,3))
res2: Boolean = false

scala> isString("hello")
res3: Boolean = true

定数パターン

定数パターンは自分自身とだけマッチする。pattern には任意のリテラルが使える。

scala> def describe(x: Any) = x match {
     |   case 1 => "one"
     |   case false => "false"
     |   case Nil => "the empty list"
     |   case _ => "others"
     | }
describe: (x: Any)String

scala> describe(1)
res4: String = one

scala> describe(true)
res5: String = others

scala> describe(Nil)
res6: String = the empty list

scala> describe(2)
res7: String = others

変数パターン

Scala の字句解析器は先頭が小文字になっている場合にパターン変数, それ以外は定数と見なす。
math に含まれるネイピア数E と 円周率Pi の定数と, Piに別名をつけた小文字から始まる変数 pi を作り試してみる。

scala> import math.{E, Pi}
import math.{E, Pi}

scala> E match {
     |   case Pi => Pi
     |   case _ => "OK"
     | }
res8: Any = OK

scala> val pi = Pi
pi: Double = 3.141592653589793

scala> E match {
     |   case pi => pi
     | }
res9: Double = 2.718281828459045

変数パターンは全てにマッチするためにワイルドカードパターンは無視される。

scala> E match {
     |   case pi => pi
     |   case _ => "OK"
     | }
:10: warning: patterns after a variable pattern cannot match (SLS 8.1.1)
         case pi => pi
              ^
:11: warning: unreachable code due to variable pattern 'pi' on line 15
         case _ => "OK"
                   ^
:12: warning: unreachable code
         case _ => "OK"
                   ^
res10: Any = 2.718281828459045

コンストラクタパターン

Scala は深いパターンマッチをサポートする。
コンストラクタは 名前付きケースクラスと, ()の中のコンストラクタパラメータで構成される。
BinOp(“+”, Number(1), Number(2)) の場合, BinOpがケースクラスで “+”, Number(1), Number(2) がコンストラクタパラメータとなる。

scala> abstract class Expr
defined class Expr

scala> case class Number(num: Double) extends Expr
defined class Number

scala> case class BinOP(operator: String, left: Expr, right: Expr) extends Expr
defined class BinOP

scala> case class BinOp(operator: String, left: Expr, right: Expr) extends Expr
defined class BinOp

scala> val op = BinOp("+", Number(1), Number(2))
op: BinOp = BinOp(+,Number(1.0),Number(2.0))

scala> op match {
     |   case BinOp("+", Number(1), Number(2)) => println("a deep match")
     |   case _ =>
     | }
a deep match

シーケンスパターン

シーケンス型に対するパターンマッチ。固定長パターンと任意長パターンがある。

scala> Seq(1,2,3) match {
     |   case Seq(_, 2, _) => println("found it")
     |   case _ =>
     | }
found it

任意長パターンの場合は *_ を使う。また, @ を使うとマッチした値の全体が取り出せる。

scala> Seq(1,2,3) match {
     |   case Seq(1, _*) => println("found it")
     |   case _ =>
     | }
found it

scala> Seq(1,2,3) match {
     |   case Seq(x @_*) => x
     | }
res11: Seq[Int] = List(1, 2, 3)

タプルパターン

異なる型を要素に持てるタプルのパターンマッチ。

scala> (-99, "a", List(1,2,3)) match {
     |   case (a, b, c) => println(a + b + c)
     |   case _ =>
     | }
-99aList(1, 2, 3)

型付きパターン

型付きパターンによる型テスト。型テストは isInstanceOf メソッドでもできる。

scala> def generalSize(x: Any) = x match {
     |   case s: String => s.length
     |   case m: Map[_, _] => m.size
     |   case _ =>  -1
     | }
generalSize: (x: Any)Int

scala> generalSize("hello")
res12: Int = 5

scala> generalSize(Map("a" -> 1, "b" -> 2))
res13: Int = 2

scala> generalSize(Seq(1,2,3))
res14: Int = -1

パターンガード

パターンガードは pattern の後ろに if から始まるガードを書く。ガードが true の場合のみ評価される。

scala> def simplifyAdd (e: Expr) = e match {
     |   case BinOp("+", x, y) if x == y => BinOp("*", x, Number(2))
     |   case _ => e
     | }
simplifyAdd: (e: Expr)Expr

scala> simplifyAdd(BinOp("+", Number(3), Number(3)))
res15: Expr = BinOp(*,Number(3.0),Number(2.0))

scala> simplifyAdd(BinOp("+", Number(2), Number(4)))
res16: Expr = BinOp(+,Number(2.0),Number(4.0))


[1] Scala match
[2] コップ本 15章 ケースクラスとパターンマッチ
[3] Constructor cannot be instantiated to expected type; p @ Person
[4] ScalaのOptionステキさについてアツく語ってみる
[5] Haskell VS. Scala