Posted at

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

More than 5 years have 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
所属:一号生

フィールドまでパターンマッチ対象にしているから

独眼鉄様にマッチしないのがわかったかな?

今回はここまでだ!


まとめ

パターンマッチ凄い奴だろ?

こんな凄い奴は使わないと損だぞ。

でも最近は、パターンマッチを書かないで綺麗に書けないかな?

と考えてたりするんだよね。。。

今回も

体で感じてくれたかな?