参考にした本は 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