S-JIS[2010-12-12/2017-02-02] 変更履歴

Scala match

Scalaのmatch式について。


概要

match式は、値に応じて処理を分岐させる。
単純に値が一致するかどうか(Javaのswitch相当)だけでなく、パターンマッチングで色々な条件でマッチさせることが出来る。

match {
  case パターン1 => 処理1
  case パターン2 => 処理2
〜
}

matchは「式」なので、値を返すことも出来る

val 変数 = 値 match {
  case パターン1 => 戻り値1
  case パターン2 => 戻り値2
〜
}

ちなみに昔は、match式は(isInstanceOf等と同様に)Anyクラスのメソッドという扱いだったそうだ。(by xuwei_kさん)[2017-01-29]
現在、専用の構文になっているのは、パターンの網羅性の確認(パターン漏れをコンパイル時に検出して警告を出す)の為らしい。(by gakuzzzzさん)

したがって、部分関数(PartialFunction)をmatch式の一部として使う(部分関数を合成してmatch式を作り出す)事は出来ない。


値に応じた分岐(switch)

整数値に応じて分岐する。
Javaのswitch文に相当する。

Scala Java相当 備考
var n = 1

n match {
  case 1 => println("いち")
  case 2 => println("に")
  case 3 => println("さん")
  case _ => println("たくさん")
}
int n = 1;

switch(n) {
case 1:  System.out.println("いち");     break;
case 2:  System.out.println("に");       break;
case 3:  System.out.println("さん");     break;
default: System.out.println("たくさん"); break;
}
Javaでは処理を終わらせたければbreakを書く必要があるので、左記の例のように1行に全処理を書くことはまず無いが。
var n = 1

n match {
  case 1 =>
    println("いち")

  case 2 =>
    println("に")

  case 3 =>
    println("さん")

  case _ =>
    println("たくさん")

}
int n = 1;

switch(n) {
case 1:
    System.out.println("いち");
    break;
case 2:
    System.out.println("に");
    break;
case 3:
    System.out.println("さん");
    break;
default:
    System.out.println("たくさん");
    break;
}
Scalaでも、(caseの「=>」以降、)次のcaseが来るまでは一連の処理と見なされる。
n match {
  case 1 =>
  case 2 =>
  case _ => error("hoge")
}
switch(n) {
  case 1:  break;
  case 2:  break;
  default: throw new RuntimeException("hoge");
}
「=>」の右側に何も書かなければ、何もしない。
n match {
  case 1 | 2 | 3 =>
    println("数えられる");
  case _ =>
    println("数えられない");
}
switch(n) {
case 1:
case 2:
case 3:
    System.out.println("数えられる");
    break;
default:
    System.out.println("数えられない");
    break;
}
Javaでは、breakが無ければ次のcaseに入るので、異なる値で同じ処理をしたい場合にcaseを並べられる。
Scalaでは「|」で区切って値を並べることが出来る。[/2010-12-25]

Javaではbreakを付けないと次のcaseも実行される(fall through/フォールスルーする)が、Scalaはそこで終わる。

Javaのdefaultに当たる(全ての条件にマッチする)のは、Scalaでは「case _」。
Javaではcaseに一致するものが無くてdefaultも無い場合は何も処理せずにswitch文の次の処理へ進むが、
Scalaでいずれのcaseにもマッチしない場合はMatchError(例外)が発生する。
明らかに足りないものがあるとコンパイラーが判断した場合はコンパイル時点でも警告が出る。[2011-04-17]
uncheckedアノテーション

この使い方(整数値で単純に分岐する)のmatch式の場合、コンパイルされると、Javaのswitch文になる。
switchアノテーション


matchは値を返す

matchも式なので、値を返す。
Javaのswitch文は値を返すような書き方は出来ない)

var n = 1

val s = n match {
  case 1 => "いち"
  case 2 => "に"
  case 3 => "さん"
  case _ => "たくさん"
}

match式の戻り型は、(if式と同様に)全てのcaseの戻り型の共通の親クラスになる。


数値以外のマッチング

Java(JDK1.6)のswitch文では数値(int以下)と列挙型(enum)しか使うことが出来ないが、
Scalaでは文字列配列タプル・リスト等もmatchで使うことが出来る。

この場合、上から順番に判定していき、マッチしたものが出た時点で「=>」の右側が処理される。

Scala コンパイルされたJavaのイメージ 備考
def nullCheck(s: String) = {
  s match {
    case null => 0
    case _    => 1 
  }
}
  caseにはnullを直接書くことが出来る。[2017-01-31]
JavaのswitchでもStringや列挙型を渡すことが出来るが、値がnullの場合はNullPointerExceptionが発生する)
def str(s: String) = {
  s match {
    case "abc" => 1
    case "def" => 2
    case "ghi" => 3
  }
}
int str(String s) {

  if ("abc".equals(s)) { return 1; } else
  if ("def".equals(s)) { return 2; } else
  if ("ghi".equals(s)) { return 3; } else
  throw new MatchError(s);
}
caseに文字列を指定した場合、コンパイルされるとif文の羅列になるのは仕方ないか。
def arr(a: Array[Int]) = {
  a match {
    case Array(1,2,3) => 1
    case Array(4,5,6) => 2
  }

}
int arr(int[] a) {
  if(a != null && a.length == 3) {
    if (a[0]==1 && a[1]==2 && a[2]==3) { return 1; } else
    if (a[0]==4 && a[1]==5 && a[2]==6) { return 2; }
  }
  throw new MatchError(a);
}
caseに配列リテラルを指定できる。
が、実際の比較はArrayクラスの==メソッドを使っているわけではない。
個々の要素を取り出して比較している。→unapplyメソッド
def tup(t: (Int, String)) = {
  t match {
    case (1,"abc") => 11
    case (2,"abc") => 21
    case (1,"def") => 12
    case (2,"def") => 22
  }
}
int tup(Tuple2 t) {
  if (t != null) {
    int    n = (Integer)t._1();
    String s = (String) t._2();
    switch(n) {
    case 1:
      if ("abc".equals(s)) { return 11; } else
      if ("def".equals(s)) { return 12; }
      break;
    case 2:
      if ("abc".equals(s)) { return 21; } else
      if ("def".equals(s)) { return 22; }
      break;
    }
  }
  throw new MatchError(t);
}
タプルのマッチ条件にIntが含まれている場合は、Javaのswitch文を使って上手く分岐してくれるようだ。
def tup(obj: Any) = {
  obj match {
    case (1, _) => "tup2-1"
    case (2, _) => "tup2-2"
    case (_, 3) => "tup2-3"
    case (_, _, _) => "tup3"
  }
}
  タプルの例。
値の一部分だけマッチしていればいい場合、
それ以外の項目は「_」に出来る。
def list(l: List[_]) = {
  l match {
    case List(1, _*) => "one"
    case List(2, _*) => "two"
  }
}
  Listの例。[2011-01-09]
引数が可変になる値の場合、「_*」で“その他”(引数が0個以上)を指定できる。
「*」を付けられるのは末尾のみで、先頭や途中の要素には使えない。[2011-02-27]

型のマッチング

マッチさせたい型の値と比較するだけでなく、型そのものを比較対象とすることが出来る。

def know(a: Any) = {
  a match {
    case _: String => "文字列"
    case _: Int    => "整数"
    case _         => "知らね"
  }
}

caseに「:型」を付けることで、その型にマッチするかどうか判定される。


型(Class)そのものを比較したい場合、caseの後ろに直接classOf[]を書くとエラーになる。[2011-03-06]

def know(c: Class[_]) = {
  c match {
    case classOf[String] => "文字列"
    case classOf[Int]    => "整数"
    case _               => "知らね"
  }
}
scala> def know(c: Class[_]) = {
     |   c match {
     |     case classOf[String] => "文字列"
<console>:3: error: '=>' expected but '[' found.
           case classOf[String] => "文字列"
                       ^

別に定数を定義すれば出来る。

def know(c: Class[_]) = {
  val ClassString = classOf[String]
  val ClassInt    = classOf[Int]

  c match {
    case ClassString => "文字列"
    case ClassInt    => "整数"
    case _           => "知らね"
  }
}

あるいは、ガード条件(if)を使うという方法も。(by xuwei_kさん)

def know(c: Class[_]) = {
  c match {
    case x if x==classOf[String] => "文字列"
    case x if x==classOf[Int]    => "整数"
    case _                       => "知らね"
  }
}

色々なクラスの値が入っているコレクションから特定のクラスのものだけ抽出したい場合はcollectが便利。[2011-08-15]

scala> val list = List("str", 123, 'symbol, "abc")
list: List[Any] = List(str, 123, 'symbol, abc)

scala> list.collect{ case s:String => s }	//Stringの値だけ抽出してList[String]を作る
res0: List[String] = List(str, abc)

意味合いとしては以下の書き方と同じ。

scala> list.filter(_.isInstanceOf[String]).map(_.asInstanceOf[String])
scala> list.flatMap{ case s:String => Some(s) case _ => None }

マッチした値を取得する

caseに変数を置くと、マッチしたときにその変数に値を入れてくれる。
この場合、その変数はval扱いとなるので、再代入不可。

Scala コンパイルされたJavaのイメージ 備考
def size(value: Int) = {
  value match {
    case 1 => "tiny"
    case 2 => "small"
    case n => n.toString
  }
}
  “その他”の場合に値を取得する例。[2017-01-29]
(つまり「case _ =>」は、全ての値にマッチするが、マッチした値を使わないことを意味する)
def opt(option: Option[String]) = {
  option match {
    case Some(s) => s
    case _       => "empty"
  }
}
  Optionの例。[2017-01-29]
要するにnull以外の場合だけ処理が出来る。
def tup(obj: Any) = {
  obj match {
    case (1, s)    => "one " + s
    case (2, s)    => "two " + s
    case (_, s, _) => "tup3 " + s
  }
}
  タプルの例。
定数・変数・ワイルドカード「_」の個数が合致し、
定数部分がマッチすれば、
変数にはその位置の値が入る。
def tup(obj: Any) = {
  obj match {
    case t @ (_,b)   => t + "=" + b
    case t @ (_,_,c) => t + "=" + c
  }
}
  タプルの例。
「@」を使うと、caseにマッチした値全体と、一部分を変数に入れたものが受け取れる。
def length(obj: Any) = {
  obj match {
    case s:String   => s.length


    case a:Array[_] => a.length


    case l:List[_]  => l.size


  }
}
int length(Object obj) {

  if (obj instanceof String) {
    final String s = (String)obj; return s.length();
  }
  if (ScalaRunTime.isArray(obj,1)) {
    return ScalaRuntime.array_length(obj);
  }
  if (obj instanceof List)   {
    final List<?> l = (List<?>)obj; return l.size();
  }
  throw new MatchError(obj);
}
型でマッチする例。
マッチするとそのローカル変数が作られる。
def list(l: List[_]) = {
  l match {
    case List(n, other @ _*) =>
      println(n)
      println(other)
  }
}
  Listの例。[2011-01-09]
“その他全て”を表す「_*」に対して、「@」で変数名を指定できる。
val re = """(.+)=(.+)""".r
def regexp(s: String) = {
  s match {
    case re(k,v) => (k,v)
    case _       => ("","")
  }
}
scala> regexp("key=val")
res44: (String, String) = (key,val)
  正規表現によるマッチングの例。[2011-01-09]
正規表現を書いた文字列のrメソッドを呼び出すと正規表現インスタンスが得られるので、それをcaseに指定する。
正規表現内の丸括弧で囲った部分がcaseで指定した変数に代入される。

変数定義時のパターンマッチ


ガード条件(if)

各caseの「=>」の前に、ifを使って条件を書くことが出来る。
その条件を満たしたときに「=>」以降が実行される。満たさなかった場合は次のcaseの評価に移る。

Scala 備考
def count(n: Int) = {
  n match {
    case m if m <  0 => "なにそれ"
    case 0           => "ない"
    case m if m <= 3 => "数えられる"
    case _           => "たくさん"
  }
}
 
def number(obj: Any) = {
  obj match {
    case n:Int                => "int=" + n
    case n:Long if n < 65536L => "小long=" + n
    case n:Long               => "大long=" + n
  }
}
型のマッチングとも一緒に使える。
def check(s: String) = {
  s match {
    case s if s ne null => s
    case _              => "empty"
  }
}
nullチェックも書ける。[2017-01-29]
が、これならOptionを使った方がいいような気もする^^;
  Option(s) match {
    case Some(s) => s
    case _       => "empty"
  }
  Option(s).getOrElse("empty")
  s match {
    case null => "empty"
    case s    => s
  }

matchの省略

場合によっては、match式の一部を省略できる。

通常版 省略版 備考
def count(n: Int) = {
  n match {
    case 1 => "one"
    case 2 => "two"
  }
}
def count(n: Int) = n match {
  case 1 => "one"
  case 2 => "two"
}
メソッド定義の式本体がmatch式しか無い場合は、式本体をくくるとげ括弧は省略できる。
って、それはメソッド定義(def)のルールであって、match式のルールではない^^;
seq.map(n => n match {
  case 1 => "one"
  case 2 => "two"
})
seq.map(_ match {
  case 1 => "one"
  case 2 => "two"
})
seq.map {
  case 1 => "one"
  case 2 => "two"
}
mapfilter等の)関数を受け取るメソッドに対し、その関数の引数でmatchしたい場合、「引数 match」を省略できる。[2017-02-02]
var f: (Int) => String = null

f = (n: Int) => {
  n match {
    case 1 => "one"
    case 2 => "two"
  }
}
var f: (Int) => String = null

f = {
  _ match {
    case 1 => "one"
    case 2 => "two"
  }
}
f = {
  case 1 => "one"
  case 2 => "two"
}
関数リテラルで式本体がmatch式しか無く、比較 元の値が関数の引数の場合、「引数 match」を省略できる。
部分関数(PartialFunction)
val f = (n: Int) => {
  n match {
    case 1 => "one"
    case 2 => "two"
  }
}
val f: (Int) => String = {
  case 1 => "one"
  case 2 => "two"
}
val f = {
  case 1 => "one"
  case 2 => "two"
}: ((Int) => String)
def call1(f: (Int) => String) = {
  f(1)
}

call1({ (n) =>
  n match {
    case 1 => "one"
    case 2 => "two"
  }
})
def call1(f: (Int) => String) = {
  f(1)
}

call1({
  case 1 => "one"
  case 2 => "two"
})


//引数が1個の場合は()の代わりに{}を使用可
call1{
  case 1 => "one"
  case 2 => "two"
}
def call2(f: (Int, Int) => Int) = {
  f(1, 0)
}

call2({ (a, b) =>
  (a, b) match {
    case (1, _) => 11
    case (_, 2) => 22
  }
})
def call2(f: (Int, Int) => Int) = {
  f(1, 0)
}

call2({
  case (1, _) => 11
  case (_, 2) => 22
})


//引数が1個の場合は()の代わりに{}を使用可
call2{
  case (1, _) => 11
  case (_, 2) => 22
}
関数リテラルの引数が2つ以上ある場合は、タプルの様にしてマッチできる。

パターンマッチの仕組み

オブジェクトの要素をマッチさせる仕組みについて。[2011-01-09]

オブジェクト(object)内にunapplyあるいはunapplySeqという名前のメソッドを定義して、caseにそのオブジェクトを書くと、unapplyまたはunapplySeqメソッドが呼ばれる。
unapply()・unapplySeq()の引数は1個で、matchで指定される判定対象の値が渡される。(対象の値の型がunapply()・unapplySeq()の引数の型に代入できない場合はコンパイルエラーとなる)
unapply()・unapplySeq()メソッドの戻り値によってマッチしたかどうかを判定する。

unapply()メソッドは抽出子(extractor)と呼ばれる。

ケースクラス配列(Array)Optionタプルは抽出子を持っているので、パターンマッチが使える。[2017-01-29]

パターンマッチの例 展開されたコード(イメージ) 備考
object MyCase {
  def unapply(a: Any): Boolean = {
    a.isInstanceOf[Int]
  }
}
引数の無いオブジェクトをcaseに指定した場合、unapply()が呼ばれる。
このunapply()の戻り型はBooleanである必要がある。
val n = 123
//val n = 123L

n match {
  case MyCase() => "match"
  case _        => "orz"
}
val n = 123
//val n = 123L

if (MyCase.unapply(n)) {
  "match"
} else {
  "orz"
}
object MyCase {
  def unapply(a: Any): Option[Int] = {
    if (a.isInstanceOf[Int]) {
      Some(a.asInstanceOf[Int])
    } else {
      None
    }
  }
}
引数があるオブジェクトをcaseに指定した場合、unapply()が呼ばれる。
このunapply()の戻り型はOptionクラスである必要がある。

このunapply()では、マッチしたときにSomeに値を入れて返す。
マッチしなければNoneを返す。
呼び出し元ではSomeかどうかを判断して分岐する。
val obj = 123

obj match {
  case MyCase(n) => n
  case _ => 0
}
val obj = 123

val opt = MyCase.unapply(obj)
if (opt.isDefined) {
  opt.get
} else {
  0
}
object MyCase {
  def unapply(a: Any) = {
    if (a.isInstanceOf[Int]) {
      Some((a.asInstanceOf[Int], "int"))
    } else {
      None
    }
  }
}
caseのオブジェクトに複数の要素(個数は固定)を指定する例。
unapply()では、caseでの引数の個数に合致した個数のタプル
Someに入れて返す。
(unapply()が返すタプルの要素数と等しい個数しかcaseに指定できない)
val obj = 123

obj match {
  case MyCase(n, s) => s+n
  case _ => "none"
}
val obj = 123

val opt = MyCase.unapply(obj)
if (opt.isDefined) {
  val (n, s) = opt.get
  s+n
} else {
  "none"
}
obj match {
  case MyCase(_, s) => s
  case _ => "none"
}
val opt = MyCase.unapply(obj)
if (opt.isDefined) {
  val t = opt.get
  val s = t._2
  s
} else {
  "none"
}
object MyCase {
  def unapplySeq(a: Any) = {
    if (a.isInstanceOf[Int]) {
      Some(Seq(a.asInstanceOf[Int],22,33))
    } else {
      None
    }
  }
}
caseに指定する要素数を可変にしたい場合、
unapply()ではなくunapplySeq()を定義する。
unapplySeq()ではSeqクラス(Listとか)をSomeに入れて返す。
val obj = 123

obj match {
  case MyCase(n1,n2,n3) =>
    n1+n2+n3
  case _ => 0
}
val obj = 123

val opt = MyCase.unapplySeq(obj)
val s = opt.getOrElse(Seq())
if (s.size == 3) {
  val i = s.iterator
  val n1 = i.next
  val n2 = i.next
  val n3 = i.next
  n1+n2+n3
} else {
  0
}
obj match {
  case MyCase(n1, _*) =>
    n1
  case _ => 0
}
val opt = MyCase.unapplySeq(obj)
val s = opt.getOrElse(Seq())
if (s.size >= 1) {
  val n1 = s.head
  n1
} else {
  0
} 

上記の例ではMyCaseのunapply()・unapplySeq()の中で引数がIntかどうかをチェックしたけれど、
普通は引数が自分自身のクラスかどうかを判定して(あるいはそもそも引数の型を自分のクラスにして)、
自分の内部で保持している値をSomeに入れて返す。

参考:


Scala構文へ戻る / Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま