2012年9月6日木曜日

[Scala] match (基本編)

多彩な機能を持つ Scala のMatch 文を見ていくことにする。

まずは基本形。

val a : Int = 2;
a match {
  case 1 => println("ONE");
  case 2 => println("TWO");
  case _ => println("OTHER: " + a);
}

いずれの条件にもマッチしなかった場合、Java でいうところの default は 「case _ 」で書く。

Java と同様に case 句の後の実行部は複数の文が書ける。break; は必要ない。break を書かなくても、いずれかでマッチしたらほかのcase 節は実行されない。

val a : Int = 2;
a match {
  case 1 => 
    print("ONE.");
    println("EINS.");
  case 2 =>
    print("TWO.");
    println("ZWEI.");
  case _ if a %2 == 0 => println("OTHER Even: " + a);
  case _ => println("OTHER" + a);
}

「case _ 」を以下のようにしてマッチしなかった場合に変数に割り当てることもできる。

val a : Int = 9;
a match {
  case 1 => println("ONE");
  case 2 => println("TWO");
  case other => println("OTHER: " + other);
}

このように書くと other に 9 が入る。
「 other 」 の部分は新しい変数なので、「other : Int」と書くこともできる。
この形式だけだと新たな変数を割り当てるメリットは特にないが、 以降の型判定等のケースではメリットが出てくる。

ちなみにこのように変数名を大文字で始めるとエラーになる。

a match {
  case 1 => println("ONE");
  case 2 => println("TWO");
  case Other => println("OTHER: " + Other);
}
- not found: value Other

意味が解らない。もちろん、Scala では変数名は val だろうが var だろうが大文字で始まること自体は問題ない。Eclipse IDE しか使っていないのだが、REPL でも同じなのだろうか?未確認。
もっともローカルな変数名を大文字で始めることもないと思うので問題ではないのだが・・・


match しなかった場合


どの条件にもマッチしなかった場合は例外になる。

val a : Int = 2;
a match {
  case 1 => print("ONE.");
}

Exception in thread "main" scala.MatchError: 2 (of class java.lang.Integer)

「case _」、「case XXX(:Any)」を書かないと何も実行されないのではなく例外扱いなので気を付けよう。


case if 形式(ガード)


case 節の後ろに if 句(ガード)をつけてマッチ条件をさらに限定することができる。

for (i <- List(1,2,3,4))
  i match {
    case 1 => println("ONE");
    case 2 => println("TWO");
    case x if x % 2 == 0 => println("Other Even: " + x);
    case x => println("OTHER: " + x);
}

ONE TWO OTHER: 3 Other Even: 4

if 句ではねられた場合も、その節ではマッチしなかったものとみなされて次の case 節へ進む。
リストなどに対するマッチングの際には、if 句による限定が有用に活用できる。


型判定


match...case 文では型の判定もできる。

for (elem: Any <- List(1, "SSS", 1.2, 1.3f, null)) {
  elem match {
    case x: String => println("String: " + x);
    case x: Int => println("Int: " + x);
    case x: Double => println("Double: " + x);
    case x => println("Other: " + x)
  }
}

Int: 1 String: SSS Double: 1.2 Other: 1.3 Other: null

今度はさっきの例と違って変数に代入しなおされているメリットがはっきりしている。
自分でわざわざダウンキャストしないでもマッチしたクラスの型になっているので手間が省ける。

各 case 節ごとに異なる変数名にする必要はない。
また、null はどの型ともマッチしない(「 case null」にはマッチする)ので注意が必要。

もちろん、変数を使わないときには、

for (elem: Any <- List(1, "SSS", 1.2, 1.3f, null)) {
  elem match {
    case _: String => println("String: ");
    case _: Int => println("Int: ");
    case _: Double => println("Double: ");
    case _ => println("Other: ")
  }
}

でもOK。


型判定と型パラメータ


型判定時の注意点。以前、変異指定と isInstanceOf のところで見たように、isInstanceOf の型判定と asInstance によるキャストはジェネリクスの型パラメータに対して無力だったのだが、ここでも同じことが当てはまる。

val a : Any = List("a");
a match {
  case iList : List[Int] =>
      val i : Int = iList(0);
      println( i );
  case other => println("Other");
}

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

このコードではコンパイル時にはエラーにならないのだが、実体が List[String] である a が 「 case iList : List[Int] 」にマッチしてしまうので実行時エラーになってしまう。



1 件のコメント:

  1. こんにちは。ほとんどの言語は識別子のイニシャルが大文字か小文字かで文法的な意味は変わりませんが、実はScalaは他の言語と違って、match文のcaseのみ、大文字か小文字かで違うんです。
    小文字なら、例えばcase x if x == 0 =>...だとxにはとりあえず何でも一致して、パターンガード(if以降、=>より前)で更にチェックされますが、case XだとXは定数(?)だとみなされて、事前にval X = 0とか書かないとエラーだそうです。
    ちょっとややこくて、私も充分には理解していません。説明不十分でごめんなさい。(^_^;)

    返信削除