LL脳がscalaの勉強を始めたよ その46


Scalaコップ本の15章を続けていきますよー、今回はパターンマッチ用のパターンを詳しく見ていきましょうかねー。ちょっと量が多いので前半後半に分けたいと思いますよー

パターンの種類

前回早足でざざっと触れたパターンを見ていきますよー。とりあえずパターンマッチサンプル用の算術式ライブラリはこいつらですね

// 抽象基底クラス
abstract class Expr
//// 以下はケースクラスの定義です
// 変数定義クラス 
case class Var(name:String) extends Expr
// 数値定義クラス
case class Number(num:Double) extends Expr
// 単項演算クラス
case class UnOp(opearator:String, arg:Expr) extends Expr
// 二項演算クラス
case class BinOp(opearator:String, left:Expr, right:Expr) extends Expr

上記をベースにして進めていきますよー

ワイルドカードパターン

あらゆるオブジェクトにマッチするパターンで"_"で表現しますねー。デフォルトとして使うことも多いっすね

def test(expr:Expr) = expr match {
  case BinOp(op, left , right) => println(expr + " は2項演算ですけど")
  // ここがワイルドカードパターンです
  case _ =>
}
|<

んじゃ実行しますよー
>|scala|
// 条件に当てはまるヤツですね
scala> test(BinOp("+", Number(1), Number(3)))
BinOp(+,Number(1.0),Number(3.0)) は2項演算ですけど

// ワイルドカードパターンにあてはまると何もしません(´・ω・`)
scala> test(UnOp("+", Number(3)))            

ワイルドカードはマッチ対象全体でなく、次のようにパターン内の要素でも使うことが出来るみたいです

def test(expr:Expr) = expr match {
  // 2項演算の各要素をワイルドカードで表現しますよ
  case BinOp(_, _ , _) => println(expr + "は2項演算だ")
  // これがデフォルト
  case _ => println("ソレ以外の何か")
}

んじゃ、実行してみますかねー

scala> test(BinOp("+", Number(1), Number(3)))
// きちんと認識してますねー
BinOp(+,Number(1.0),Number(3.0))は2項演算だ

パターン内の要素に対して特に条件がない場合は変数パターンを使わないで、ワイルドカードを使っちゃうのがいいかも知れませんな

定数パターン

任意のリテラルを利用して完全一致でマッチングするヤツですねー。パタンとして任意のValやシングルトンオブジェクトを定数として使うことが出来るみたいです。

サンプルはこんな感じですねー

def describe(x:Any) = x match {
  // 整数との一致
  case 5 => "五"
  // 真偽値との一致ですね
  case true => "真"
  // 文字列でも完全一致
  case "hello" => "はろー"
  // 空リストにマッチするシングルトンオブジェクトデス
  case Nil => "空のリストですよ"
  // デフォルトパターンです
  case _ => "ソレ以外の何か"
}

んじゃ実行してみます

// 数字マッチ
scala> describe(5)
res0: java.lang.String = 五
// Boolマッチ
scala> describe(true)
res1: java.lang.String = 真
// 文字列マッチ
scala> describe("hello")
res2: java.lang.String = はろー
// Nilマッチ
scala> describe(List()) 
res3: java.lang.String = 空のリストですよ

// デフォルトです
scala> describe()       
res4: java.lang.String = ソレ以外の何か

上のサンプルみたいにパターンマッチする値をAny型で指定しておいて、評価時型を気にせずにマッチング出来るってのはかなり便利な感じですねー

変数パターン

変数パターンをつかったマッチングはワイルドカードパターンと同じように任意のオブジェクトになるのだけども、マッチしたオブジェクトを変数で束縛して使えるように出来るのが特徴ですね。

例えばデフォルトケースで使ってみるとこんな感じですね

def testMatch(x:Any) = x match {
  case 0 => "零"
  case param => "零以外の" + param
}

んじゃ、実行しますよー

// マッチするパターンですね
scala> testMatch(0)  
res8: java.lang.String = 零

// デフォルトケースですがパラメータと組み合わせた値が帰ってきましたよ
scala> testMatch(5432)
res9: java.lang.String = 零以外の5432

変数を使って束縛したオブジェクトは加工したりなんだり自由自在ですな(`・ω・´)

定数か変数か

定数パターンはシンボル名を持つことが出来るんだけども、変数パターンとの混同はどんな塩梅になっておりますか?ということを見ていきたいと思います。

Scalaでは字句解析ルールとして先頭が小文字になっている単純名はパターン変数、そうでないものは定数としてみなすみたいなので、今回は定数値E(自然対数)とPi(円周率)で試してみますかねー

とりあえずこんな感じで定数EとPiのマッチングをしてみますよ

// importしますよ
scala> import Math.{E, Pi}
import Math.{E, Pi}

// マッチしないでデフォルトパターンに流れるはず
scala> E match {
         // 渡されたオブジェクトがPiと同じかどうか
     |   case Pi => "異なる数学記号ですが Pi = " + Pi
     |   case _ => "OK"
     | }
// きちんとデフォルトパターンになりましたよ
res10: java.lang.String = OK

これだとOKですね。次にpiという小文字変数パターンを使ってみますかね

// インポートしますよ
scala> import Math.{E, Pi}
import Math.{E, Pi}
// 変数に格納しますよ
scala> val pi = Math.Pi
pi: Double = 3.141592653589793
// マッチングです
scala> E match {
     |   case pi => "異なる数学記号ですが Pi = " + pi
     |   case _ => "OK"
     | }
// パターン的にデフォルトケースに行かないよ!と怒られました。
<console>:9: error: unreachable code
         case _ => "OK"
                   ^

どうやら1行目が変数パターンとして認識されているので、全てのオブジェクトに当てはまってしまうから、2行目のデフォルトケースには行かないよ!というコンパイルエラーっぽいですね。

とりあえずエラー回避でこんな感じにやってみますかねー

// インポートしますよ
scala> import Math.{E, Pi}
import Math.{E, Pi}
// 変数に格納しますよ
scala> val pi = Math.Pi
pi: Double = 3.141592653589793

// マッチングです
scala> E match {
     |   case pi => "異なる数学記号ですが Pi = " + pi
     | }
// PiとEが同じだよ!という間違った結果に。。。。
res12: java.lang.String = 異なる数学記号ですが Pi = 2.718281828459045

実行できたけども間違った結果になりましたねー。どうやらパターンマッチ式の中で変数パターンとして束縛された変数はスコープ的に上書きされちゃうみたいですねー。これは結構注意が必要かもしれませんねc⌒っ゚д゚)っφ メモメモ...

上記のようなことを防ぐ(先頭小文字変数のものを定数パターン的に使う)場合は次のようにバッククォートで囲むことで防げるみたいです。

// インポートしますよ
scala> import Math.{E, Pi}
import Math.{E, Pi}
// 変数に格納しますよ
scala> val pi = Math.Pi
pi: Double = 3.141592653589793

// マッチングです
scala> E match {
         // バッククォート囲みで定数アピールしてみますよ
     |   case `pi` => "異なる数学記号ですが Pi = " + pi
     |   case _ => "OK"
     | }
res13: java.lang.String = OK

これでOKですね(`・ω・´)

バッククォート囲みでのキーワードの定数化は結構いろんなところで使うみたいですね...6章10節とかでもやったみたい... Thread.`yield`()とかって、うん確かに観たことあるな。

コンストラクタパターン

クラスのコンストラクタを利用してパターンマッチするようなのデス(`・ω・´)(という解釈はあってるのかしら?不安)

とりあえず何度か出てるこんな感じのパターンですね

def test(expr:Expr) = expr match {
  case BinOp("+", e, Number(0)) => println("さりげなく3階層のマッチングしてますよ")
  case _ => 
}

// 実行しますよ
scala> test(BinOp("+", Number(1), Number(0)))
さりげなく3階層のマッチングしてますよ

コンストラクタパターンではパターの要素の中にパターンを使うことで複雑なパターンマッチを行うことが出来る協力マッチングらしいです。例えば上記サンプルパターンでは次の3階層のチェックを行っておりますよ

  • BinOpの形式になっているかどうか
  • 第1パラメーターが"+", 第3パラメーターがNumberかどうか
  • 第3パラメーターNumberの値が0かどうか

うん、使いこなせれば協力なツールになりそうだな(`・ω・´)

シーケンスパターン

ListやArrayなんかでマッチングするパターンですな、こんな感じで特定の要素のみのマッチングが出来るみたいデス

def test(list:Any) = list match {
  case List(0, _, _) => println("リストだな")
  case _ =>
}

//// 実行結果ですよ
// 先頭が0で要素が3つのリストであればマッチしますな
scala> test(List(0,3,4))
リストだな

ちなみに上のように固定長にせずに*省略で任意の長さのマッチングもできますネ

// 可変長にしてみた
def test(list:Any) = list match {
  case List(0, _*) => println("リストだな")
  case _ =>
}

//// 実行結果ですよ
// 先頭が0であればマッチしますな
scala> test(List(0))
リストだな
scala> test(List(0,1))
リストだな
scala> test(List(0,1,2))
リストだな
タプルパターン

リストやArrayができるならタプルでも!ってことで、異なる型要素のタプルでのマッチングも可能ですねってサンプルです

def testTuple(tuple:Any) = tuple match {
  case (a, b, c) => println("タプルデス。" + a + b + c + "です")
  case _ => 
}

// 実行結果ですよ、要素が3つのタプルですよ
scala> testTuple("C", 3, "PO")   
タプルデス。C3POです

前半はいじょうー

とりあえずパターンマッチングの前半はこんな感じです。結構イロイロなマッチングが出来るもんですが、次回は型も絡めたマッチングのアレやコレをやりたいと思いますよー

夏休み進行ですが…できるだけ頑張ります(´・ω・`)