Java
Scala
関数型言語

第18章:Scalaのパターンマッチ

More than 1 year has passed since last update.

今回は パターンマッチ だ!!
こいつ凄い奴なんだぜ。

実は最初、全く使い道がわからなかった。
ifで良いんじゃね?とか思ってけど、
ifよりもやれることの幅が広すぎるんだ。

書きながら見てみよう!

パターンマッチとは

ifやswitchのように条件分岐に分類されるのかな。
ただifやswitchと同等に使うだけではもったいない。

どれだけ凄いか見てみよう!

パターン

意識してなかったけどパターン色々あるんだね。。。

最初は戸惑うかもしれないけど、簡単なパターンから見てみよう。

定数パターン

まずは、Javaのswich文ぽいやつ。

PatternMatchConst.scala
import scala.util.Random

object PatternMatchConst {

  def main(args: Array[String]): Unit = {
    val random = Random.nextBoolean

    val num = random match {
      case true => 1
      case false => 0
    }

    println("random:" + random)
    println("number:" + num)
  }

}

動かしてみるよ!

$ scalac PatternMatchConst.scala

$ scala PatternMatchConst
random:true
number:1

ここではランダムなBoolean を生成して変数random に束縛している。
そして変数random に対してパターンマッチを実施する。
それがcase の後のtruefalse だ。

そしてマッチした方の値を変数num に束縛させる。

パターンマッチも式であることを意識するために
戻り値を返し変数に束縛させるソースコードにしてみた。

束縛とは

本とかだと、以下のような記述が良く出てくる。

値を変数に束縛する

式で得た結果を変数に紐づけるという感じなのかな。
代入と似ているけど、考え方から違うんだよね。

bind の訳として用いられているね。

今回はパターンマッチが本題なので、詳しく知りたい場合は以下を読んでみて下さい。

Smalltalk use: better《余録》代入 vs. 束縛

型パターン

次は だ。Javaのinstanceofと似ている。

PatternMatchType.scala
object PatternMatchType {

  def main(args: Array[String]): Unit = {
    val list = List(1, true, "J")

    for (value <- list) {
      value match {
        case num: Integer => println("num=" + num)
        case bool: Boolean => println("bool=" + bool)
        case name: String => println("name=" + name)
        case _ => println("value=" + value)
      }
    }

  }

}
$ scalac PatternMatchType.scala

$ scala PatternMatchType
num=1
bool=true
name=J

型に応じてやることを決めているんだ。

ワイルドカードパターン

つぎはワイルドカード_だ。

PatternMatchWild.scala
object PatternMatchWild {

  def main(args: Array[String]): Unit = {
    val num = 4

    num match {
      case _ => println("number:" + num)

    }
  }

}
$ scalac PatternMatchWild.scala

$ scala PatternMatchWild
number:4

全てのパターンにマッチしちゃうんだ。

ワイルドカードパターンがない場合

どうなるか見てみよう。

PatternMatchNoWild.scala
object PatternMatchNoWild {

  def main(args: Array[String]): Unit = {
    val list = List(1, true, "J")

    for (value <- list) {
      value match {
        case num: Integer => println("num=" + num)
        case bool: Boolean => println("bool=" + bool)
      }
    }

  }

}
$ scalac PatternMatchNoWild.scala

$ scala PatternMatchNoWild
num=1
bool=true
scala.MatchError: J (of class java.lang.String)
    at PatternMatch3_2$$anonfun$main$1.apply(PatternMatch3_2.scala:7)
    at PatternMatch3_2$$anonfun$main$1.apply(PatternMatch3_2.scala:6)
    at scala.collection.immutable.List.foreach(List.scala:318)
    at PatternMatch3_2$.main(PatternMatch3_2.scala:6)
    at PatternMatch3_2.main(PatternMatch3_2.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at scala.tools.nsc.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClassLoader.scala:71)
    at scala.tools.nsc.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:31)
    at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:139)
    at scala.tools.nsc.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:71)
    at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:139)
    at scala.tools.nsc.CommonRunner$class.run(ObjectRunner.scala:28)
    at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:45)
    at scala.tools.nsc.CommonRunner$class.runAndCatch(ObjectRunner.scala:35)
    at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:45)
    at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:74)
    at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:96)
    at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:105)
    at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

こんな感じになっちゃいました。
何かしらのパターンにマッチしないといけないんだね。

別の手段で制御しないのであれば、ワイルドカードパターンを最後に書くようにしよう。

変数パターン

次は変数に束縛させてみるよ。ここからJavaの世界から離れるね。

PatternMatchVar.scala
object PatternMatchVar {

  def main(args: Array[String]): Unit = {
    val random = 4

    val result = random match {
      case num => num * 2
    }

    println("random:" + random)
    println("number:" + result)
  }

}
$ scalac PatternMatchVar.scala

$ scala PatternMatchVar
random:4
number:8

まずこのパターンは全ての値にマッチするんだ。
しかもマッチした値を 変数 num に束縛させているんだ。

そして処理の中で使用している。

変数num を2倍にしているんだ。

ワイルドカードパターンと似ているけど、別の変数に束縛していところが違うんだ。

シーケンスパターン

なんとシーケンスともマッチングできるんだ。

PatternMatchSeq.scala
object PatternMatchSeq {

  def main(args: Array[String]): Unit = {
    val seq = Seq(1, 2, 3, 4)

    val result = seq match {
      case Seq(1, a, _*) => a
      case _ => 0
    }

    println("number:" + result)
  }

}
$ scalac PatternMatchSeq.scala

$ scala PatternMatchSeq
number:2

ここでは先頭要素が1 で、要素数2個以上のSeq という条件でマッチングしている。
_* は0個以上の任意の要素を表すんだ。

そして、1番目の要素を変数a に束縛しているんだ。

タプルパターン

タプルでももちろんできるぞ。

PatternMatchTuple.scala
object PatternMatchTuple {

  def main(args: Array[String]): Unit = {
    val tuple = (1, 2, "OK")

    val result = tuple match {
      case (1, 2, x) => x
      case _ => "NG"
    }

    println("result:" + result)
  }

}
$ scalac PatternMatchTuple.scala

$ scala PatternMatchTuple
result:OK

もう馴れてきたかな?

コンストラクタパターン

ケースクラスと組み合わせて使うパターンだ。
フィールドまでマッチングできるようになるんだ。

PatternMatchConstructor.scala
object PatternMatchConstructor {

  case class Person(name: String, age: Int, education: String)

  def main(args: Array[String]): Unit = {
    val person = Person("椿山 清美", 15, "男塾")

    val belongTo = person match {
      case Person("独眼鉄", _, "男塾") => "鎮守直廊三人衆 "
      case Person("椿山 清美", _, "男塾") => "一号生"
      case _ => "一般市民"
    }

    println("所属:" + belongTo)
  }

}
$ scalac PatternMatchConstructor.scala

$ scala PatternMatchConstructor
所属:一号生

フィールドまでパターンマッチ対象にしているから
独眼鉄様にマッチしないのがわかったかな?

今回はここまでだ!

まとめ

パターンマッチ凄い奴だろ?
こんな凄い奴は使わないと損だぞ。

でも最近は、パターンマッチを書かないで綺麗に書けないかな?
と考えてたりするんだよね。。。

今回も
体で感じてくれたかな?