09.パターンマッチ入門　 Scala基礎コース

# 目次

* 【実習】match 式について
* 【実習】パターンをまとめる
* 【実習】パターンマッチによる値の取り出し
* 【実習】入れ子のパターンマッチ
* 【実習】型によるパターンマッチ
* 【講義】パターンマッチの落とし穴

# 【実習】match 式について

ここまでで if 式、 while 式、 for 式という JavaScript でも存在した構文を学んできた。
今回は Scala でよく利用される match 式と、この match 式を使ったパターンマッチの利用法を学んでいく。

match 式の説明の前に、まずは IntelliJ IDEA で前回作成した `scala-grammar` プロジェクトを開こう。

Project ツールウィンドウの `scala-grammer/src/main/scala` フォルダを選択して、右クリックから New > Scala Class を選択し、

* Name を `Match`
* Kind を Object

それぞれ選択して `Enter` を押して、 `Match.scala` に Match オブジェクトを作成しておく。

```scala:src/main/scala/Match.scala
object Match {

}
```

以上のようなオブジェクトが作られて入れあ問題ない。

match 式は、以下のような書式で記述される。

```scala
マッチ対象の式 match {
  case パターン1 [if ガード1] => 式1
  case パターン2 [if ガード2] => 式2
  case パターン3 [if ガード3] => 式3
  case ...
  case パターンN => 式N
}
```

「マッチ対象となる式」の後ろに `match` を書いてその後、 {} 式の中に case 句を利用して分岐を記述する。
JavaScript でいう switch 文に似たような書き方となる。

具体例を知るために以下のコードを、右クリックの Scala REPL... で REPL にて評価してみよう。

```scala:REPL
val taro = "Taro"
taro match {
  case "Taro" => "Male"
  case "Jiro" => "Male"
  case "Hanako" => "Female"
}
```

評価後、

```scala:REPL
res0: String = Male
```

以上のように表示されたのではないだろうか。

定数 taro には文字列の `"Taro"` が入っており、これが match 式の `case "Taro"` にマッチし `"Male"` がこの match 式全体の値として評価されている。
`case` で指定した値にマッチすると `=>` の右側の式が評価される。

次に、 Match オブジェクトに以下のメソッドを実装してみよう。

```scala
  def intToName(num: Int): String = {
    num match {
      case 1 => "one"
      case 2 => "two"
      case _ => "other"
    }
  }
```

この実装では、 num で Int 型の整数を引数に受け取ってそれを match 句で String 型の名称に変更している。

Rerun を使って REPL で以下をそれぞれ評価してみよう。

```scala:REPL
scala> Match.intToName(1)
res0: String = one

scala> Match.intToName(2)
res1: String = two

scala> Match.intToName(3)
res2: String = other
```

それぞれ、このように評価される。
1 と 2 のマッチに関しては分かると思うが、 3 の場合は、 `case _` で書かれている case 句の値が評価される。
この `_` は、あらゆるものにマッチするパターンで、このパターンをワイルドカード パターンと呼ぶ。
パターンの漏れがないように、 match 式には、よくワイルドパターンを用意する。

# 【実習】パターンをまとめる

JavaScript の switch 文をよく知っている人は、 match 式のマッチがいわゆるフォールスルーの動作をしないことに違和感があるかもしれない。
フォールスルーとは、 switch 文の case 句の中の処理で、 break 句を書かないとそのまま次の case 句を処理することを言う。

match 式の場合は、 JavaScript と swtich 文と違い、 break 句を書かなくても次の処理が実行されてしまうようなことはない。

```scala
"abc" match {
  case "abc" => println("first")   // ここで処理が終了
  case "def" => println("second") // こっちは表示されない
}
```

上記の式を評価すると最初にマッチした、 `println("first")` の処理のみが実行され、

```scala:REPL
first
```

とだけコンソールに出力される。
REPL で試してみよう。

ただしこの動きでは、全く同じ動きをさせたい処理の表現には面倒臭い仕様のように思えてしまうだろう。

以下のメソッドを Match オブジェクトに記述してみよう。

```scala
  def message(message: String): String = {
    message match {
      case "good" => "game"
      case "bad" => "game"
    }
  }
```

このような場合、 `"game"` という文字列を 2 回記述しなくてはいけない。

ただし、 Scala の match 式ではこのような同じ値を返せば良い処理の場合には、 `|` (パイプ) を利用して複数のパターンをまとめることができる。

```scala
  def message(message: String): String = {
    message match {
      case "good" | "bad" => "game"
    }
  }
```

以上のように書き直して、 REPL で評価してみよう。

```scala:REPL
scala> Match.message("good")
res0: String = game

scala> Match.message("bad")
res1: String = game
```

以上のように、複数のパターンにマッチする case 句を `|` を使って記述できる。

# 【実習】パターンマッチによる値の取り出し

だんだん match 式の使い方がわかってきたのではないだろうか。
実は match 式にはもう 1 つ機能があり、 match した値の中から特定の値をマッチさせて取り出す、という機能がある。

ここではシーケンスを使って説明してみよう。

以下のメソッドを実装して、評価してみよう。

```scala
  def patternMatch(): Unit = {
    val seq = Seq("A", "B", "C", "D", "E")
    seq match {
      case Seq("A", b, c, d, e) =>
        println("b = " + b)
        println("c = " + c)
        println("d = " + d)
        println("e = " + e)
      case _ =>
        println("nothing")
    }
  }
```

評価すると、

```scala:REPL
scala> Match.patternMatch
b = B
c = C
d = D
e = E
```

以上のように出力される。

説明する。
ここでは Seq の先頭要素が `"A"` で 5 要素のパターンにマッチすると、残りの b, c, d, e の変数にそれぞれ Seq の 2 番目以降の要素が束縛されて、 `=>` 以降の右辺の式が評価される。

b, c, d, e の変数名は、 Scala で変数名にできるものならば何度も良く、 i1, i2, i3, i4 のようは変数名でも束縛して利用できる。

ここで初めて「束縛」という言葉が出てきたが、このような引数や定数などの名前に対して特定の値を固定することを束縛という。

```scala
      case Seq("A", b, c, d, e) =>
        println("b = " + b)
        println("c = " + c)
        println("d = " + d)
        println("e = " + e)
```

以上のような束縛した値を利用して処理を記述する。
なおパターンマッチの処理の記述は、 {} で囲ってやる必要は必ずしもない。
このように、改行だけを使って記述することもできる。

またここで束縛した値を利用して、 if ガード句を書くこともできる。
これは、 for 式の時のものと同じで、マッチする条件をフィルタリングするもの。
以下のように case 句の後に `if b != "B"` というガード句を追加してみよう。

```scala
  def patternMatch(): Unit = {
    val seq = Seq("A", "B", "C", "D", "E")
    seq match {
      case Seq("A", b, c, d, e) if b != "B" =>
        println("b = " + b)
        println("c = " + c)
        println("d = " + d)
        println("e = " + e)
      case _ =>
        println("nothing")
    }
  }
```

このガード句は、変数 b が "B" ではない時にだけマッチするようにするためのもの。
評価すると、

```scala:REPL
scala> Match.patternMatch
nothing
```

ワイルドカードパターンの方がマッチするため、 nothing がコンソールには出力される。
このように利用できる。

# 【実習】入れ子のパターンマッチ

このパターンマッチは、入れ子となったコレクションに対しても行うことが可能。

以下のメソッドを Match オブジェクトに書き加えてみよう。

```scala
  def patternMatchNest(): Unit = {
    val seq = Seq(Seq("A"), Seq("B", "C", "D", "E"))
    seq match {
      case Seq(a@Seq("A"), x) =>
        println(a)
        println(x)
      case _ => println("nothing")
    }
  }
```

この seq は、 `Seq("A")` と `Seq("B", "C", "D", "E")` の 2 要素からなるシーケンス。

ここで、 match 式を使うことで、先頭が `Seq("A")` であるという入れ子になったシーケンスのパターンを記述できていることが分かる。
また、パターンの前に `@` がついているのは **as パターン** と呼ばれる。
　`@` の後に続くパターンにマッチする式を `@` の前の変数に束縛する。

as パターンは、パターンが複雑な場合に便利。
これを用いると、パターンの一部だけを切り取ることができる。

このメソッドを評価すると、

```scala:REPL
scala> Match.patternMatchNest
List(A)
List(B, C, D, E)
```

以上のように、 as パターンを使って入れ子のパターンマッチにおける、欲しい要素に対する変数の束縛を行うことができる。

なおここで用いたような束縛は Seq だけに限らず、 Map や、タプルといったような構造の値に対しても同様のパターンマッチを行うことが可能。

# 【実習】型によるパターンマッチ

パターンとしてコレクションを対象にしたパターンマッチを行なってきたが、特定の型に所属する値に関してもその型を利用してパターンマッチができる。
その場合には、 `名前: マッチする型` の形で case 句を記述する。

例として、以下のようなメソッドを書いて評価してみよう。

```scala
  def patternMatchType(): Unit = {
    val obj: AnyRef = "String Literal"
    obj match {
      case v: java.lang.Integer => println("Integer!")
      case v: String => println(s"String! length is ${v.length}")
    }
  }
```

以上のコードでは obj は `String` 型なので、 `java.lang.Integer` という型にはマッチせず、「 String! length is 14 」という文字列がコンソールに出力される。

補足だがここで出てきた AnyRef という型は、 Scala においてすべてのオブジェクトが含まれる型。
Seq や String というものは AnyRef 型でもある。
Java でいう Object という型に対応した Scala の型だが、これに関しての詳しい説明はまた別途行う予定。

評価すると以下のようになるので試してみよう。

```scala
scala> Match.patternMatchType
String! length is 14
```

上の式では、パターンマッチが行われることで、 v は String 型に自動的に AnyRef からキャストされる。

キャストとは、値を捉える時の型を別な型に変換することを言う。
このようにパターンマッチではキャストが自動的に行われるため、 String 型で行われたパターンマッチの処理の中では、 String 型の length というメソッドが呼び出せるようになっている。

# 【講義】パターンマッチの落とし穴

この型を使ったパターンマッチには 1 つだけ落とし穴がある。
ジェネリクスという機能でのパターンマッチはうまく動かない。

ジェネリクスとは、 `Seq[Int]` と `Seq[String]` のようなコレクションの型において `[]` で指定している、中に入る要素の型の指定のことを言う。
これについては今後また詳しく説明するが、このジェネリクスの型を使ってのパターンマッチを行うことはできない。

Match オブジェクトに以下のメソッドを記述してみよう。

```scala
  def patternMatchGenerics(): Unit = {
    val obj: AnyRef = Seq("a", "b", "c")
    obj match {
      case v: Seq[Int] => println("Seq[Int]")
      case v: Seq[String] => println("Seq[String]")
    }
  }
```

型の状況を考えると「 Seq[String] 」と出力されて欲しいところだが、評価してみると、

```scala:REPL
scala> Match.patternMatchGenerics
Seq[Int]
```

このように、 Seq[Int] 型のところでマッチしてしまう。
これは、 Scala が動いている JVM の仕様で、型として利用される時には Int のジェネリクスの型の情報が消えてしまうためにこのようなことが起こっている。

なので、ジェネリクスがあるコレクションに対してのパターンマッチは行わず、

```scala
  def patternMatchGenerics(): Unit = {
    val obj: AnyRef = Seq("a", "b", "c")
    obj match {
      case v: Seq[_] => println("Seq[_]")
      case _ => println("nothing")
    }
  }
```

必ずこのように、 `Seq[_]` のような記述にして、ジェネリクスの部分にはワイルドカードの `_` を型として利用するようにしよう。

以上が、 match 式とそれを利用したパターンマッチの使い方。

# まとめ

* match 式を使って場合分けの処理を記述できる
* パターンマッチを使って値を取り出すことができる
* コレクションや型を使ってパターンマッチできる
* コレクションのジェネリクスの型を使うマッチはできない

# 挑戦

## 初級

```scala
  def printRandomChars(): Unit = {
    for (i <- 1 to 1000) {
      val chars: Seq[Char] = new scala.util.Random(new java.security.SecureRandom()).alphanumeric.take(5).toList
      val result = chars match {
        case Seq(a, _, _, _, e) if a == e => chars
        case Seq(a, b, c, d, e) if a != e => Seq(a, b, c, d, a)
      }
      println(result)
    }
  }
```

## 中級

```scala
  def last(seq: Seq[Int]): Int = {
    seq match {
      case _::xs if xs != List() => last(xs)
      case x::xs if xs == List() => x
    }
  }
```

## 上級

```scala
  def reverse(seq: Seq[Int]): Seq[Int] = {
    val result = Seq()
    seq match {
      case Seq(x) => Seq(x)
      case x::xs => reverse(xs) :+ x
    }
  }
```