🔎
Scalaパターンマッチ入門:コンストラクタ・シーケンス・タプルを体感する
Scala の魅力のひとつに 強力なパターンマッチ があります。
本記事では REPL を使いながら、以下の3種類のパターンを体感していきます。
- コンストラクターパターン
- シーケンスパターン
- タプルパターン
「写経して動かすと直感的に理解できる」形式でまとめてみました。
1. 準備:Scala REPL を開く
ターミナルで以下を実行します。
% scala
これで対話環境が起動します。あとはコピペで大丈夫です。
2. コンストラクターパターン
まずは「式がオブジェクトのコンストラクタ形と一致するか」を判定するパターンです。
例えば以下の簡単な式クラスを定義します。
scala> sealed trait Expr
trait Expr
scala> case class BinOp(op: String, left: Expr, right: Expr) extends Expr
class BinOp
scala> case class Number(value: Int) extends Expr
class Number
scala> case object VarX extends Expr
object VarX
BinOp は二項演算子、Number は数値、VarX は変数を表すものとします。
ここでパターンマッチ!
scala> val expr: Expr = BinOp("+", Number(1), Number(0))
val expr: Expr = BinOp(+,Number(1),Number(0))
scala> expr match {
| case BinOp("+", _, Number(0)) => println("加算の右がゼロ -> 簡略化できる!")
| case BinOp("*", _, Number(1)) => println("掛け算の右が1 -> 簡略化できる!")
| case _ => println("その他")
| }
加算の右がゼロ -> 簡略化できる!
「構造そのもの」にマッチする感覚が分かると思います。
3. シーケンスパターン
リストや配列に対して、長さや要素に基づいてマッチできます。
scala> def describeList(xs: List[Int]) = xs match {
| case List(0, _, _) => "先頭が0で長さ3"
| case List(1, _*) => "先頭が1で残りはなんでもOK"
| case Nil => "空リスト"
| case _ => "その他"
| }
def describeList(xs: List[Int]): String
動かしてみましょう。
scala> describeList(List(0, 2, 3))
val res1: String = 先頭が0で長さ3
scala> describeList(List(2, 5, 6, 7))
val res2: String = その他
scala> describeList(Nil)
val res3: String = 空リスト
_* が「残り全部」というシンタックスで、かなり直感的に書けます。
4. タプルパターン
複数要素をまとめるタプル (a, b, c) に対してもパターンマッチできます。
scala> def tupleDemo(expr: Any) = expr match {
| case (a, b, c) => s"3要素タプル: $a, $b, $c"
| case _ => "その他"
| }
def tupleDemo(expr: Any): String
動作確認:
scala> tupleDemo(("hello", 42, true))
val res0: String = 3要素タプル: hello, 42, true
変数を一気に分解できるので、関数の戻り値処理にも便利です。
まとめ
- コンストラクターパターン: オブジェクトの構造に直接マッチできる(代数的データ型と相性抜群)
- シーケンスパターン: List/Array に自然な形でマッチ(_* で残り全部キャッチ)
- タプルパターン: 複数要素を一気に分解できる
Scala のパターンマッチは 「if文やswitchの代わり」以上の威力 を持っています。
REPL で試すと理解が一気に深まりますので、ぜひ写経して「おぉ〜」を体験してみてください。
Discussion