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


Scalaコップ本の15章パターンマッチの部分を続けていきますよー。今回は型を絡めたパターンマッチですねー

パターンの種類

パターンの種類の詳細についての後半をやりますよー

型付きパターン

型付きのパターンマッチもできますね。まずはサンプルを見てみますかね

def test(x:Any) = x match {
  // 文字列型判定です
  case x:String => println(x + "は文字列ですよ")
  // 整数型判定です
  case x:Int => println(x + "は整数ですよ")
  // マップ型判定です
  case x:Map[_, _] => println(x + "はマップですよ")
  case _ =>  println( "想定外の値です")
}

ちょっくら実行してみますよ、セレクター式(渡された変数)の型で振り分けられる様子が分かりますねー

scala> test("hello")
helloは文字列ですよ

scala> test(3)      
3は整数ですよ

scala> test(Map(1 -> "Hello", 2 -> "World"))
Map(1 -> Hello, 2 -> World)はマップですよ

scala> test('a')                            
想定外の値です

上みたいに型での振り分けが出来れば、例えば次のような変数のサイズを求めるパターンマッチが行えるわけですな

def size(x:Any) = x match {
  // 文字列なら長さを返しマス
  case x:String => x.length
  // 整数ならそのまま値を返しマス
  case x:Int => x
  // マップならそのサイズを返しマス
  case x:Map[_, _] => x.size
  // 想定外のばあいは-1を返しますね
  case _ => -1
}

// 実行結果ですよ
scala> size("Hello")
res4: Int = 5

scala> size(3)      
res5: Int = 3

scala> size(Map(1 -> "Hello", 2 -> "World"))
res6: Int = 2

scala> size('a')                            
res7: Int = -1

こんな感じで型による振り分け処理も実装することが出来るわけですね(`・ω・´)


ちなみに同じような内容は型テストと型キャストを駆使することで実装できますが…とてもめんどくさい(´・ω・`)どれくらい面倒かを実感してみましょうか...サンプルとして上のサイズ判定関数を型テスト&型キャストで実装してみますよ

def size(x:Any) = {
  //// 文字列の判定をしますよ
  // まずは型テストです
  if(x.isInstanceOf[String]){
    // 型キャストを行って長さを取得しますよ
    x.asInstanceOf[String].length
  //// 整数の判定をしますよ
  }else if(x.isInstanceOf[Int]){
      x.asInstanceOf[Int]
  //// マップの判定をしますよ
  }else if(x.isInstanceOf[Map[_, _]]){
      x.asInstanceOf[Map[_, _]].size
  //// 想定外デス
  }else {
      -1
  }
}

行数もそうですがネストが出来るのが欝陶しいですね…型テスト(x.isInstanceOf[<型>])と型キャスト(x.asInstanceOf[<型>])が連続で出てくるのもね(´・ω・`)

上記めんどくさい例と同等である型付きパターンマッチは、逆に型キャストされた変数を扱うことができるワケですね(`・ω・´)すげー

型消去

先程のサンプルではMapの要素をワイルドカード使ってましたが、このときに特定の要素型チェックが出来るのん?って話です。

例えばこんな感じでMapの要素型までパターンマッチで指定してやります

// 要素がInt -> IntになっているMapかどうかを判定しますよ
def isIntIntMap(x:Any) = x match {
  case m:Map[Int, Int] => true
  case _ => false
}

実行してみますよ

// Int -> Intなのでtrueが返ってきますねー
scala> isIntIntMap(Map(1 -> 1, 2 -> 2))
res12: Boolean = true

// String -> Stringなのにtrueが返ってきますねー、ダメですね(´・ω・`)
scala> isIntIntMap(Map("hello" -> "hoge", "world" -> "moge"))
res13: Boolean = true

結論からいうとダメダメでした(´・ω・`)

これは「ScalaJavaと同様の消去モデルのジェネリックプログラミングを使っているため、実行時に型引数の情報を管理しないので与えられたMapオブジェクトの要素型を確かめられない」ということみたいデス。おもいきり崩してしまうと、システムはMapかどうかの判定しかしないよヽ( ・∀・)ノってことみたいですね。

ちなみに上記サンプルをコンパイルすると(要素型とか)チェックしないよWarningが出るので要チェックですな

scala> def isIntIntMap(x:Any) = x match {
     |   case m:Map[Int, Int] => true
     |   case _ => false
     | }
// unchecked warningsが出力されますデス
warning: there were unchecked warnings; re-run with -unchecked for details
isIntIntMap: (Any)Boolean

unchecked warningsはインタープリター実行時に"-unchecked"オプションをつけると詳細情報が表示されますねー

// -uncheckedオプションをつけて起動しますよ
% scala -unchecked
Welcome to Scala version 2.7.7final (OpenJDK Client VM, Java 1.6.0_18).
Type in expressions to have them evaluated.
Type :help for more information.

scala> def isIntIntMap(x:Any) = x match {
     |   case m:Map[Int, Int] => true
     |   case _ => false
     | }
<console>:5: warning: non variable type-argument Int in type pattern is unchecked since it is eliminated by erasure
        // Mapの要素型はチェックしないよWarningでした
         case m:Map[Int, Int] => true
                ^
isIntIntMap: (Any)Boolean

ちなみにListやSetなんかもダメダメですねー

// Listでもunchecked warningを出されました
scala> def isIntList(x:Any) = x match {
     |   case x:List[Int] => true
     |   case _ => false
     | }
<console>:5: warning: non variable type-argument Int in type pattern is unchecked since it is eliminated by erasure
         case x:List[Int] => true
                ^
isIntList: (Any)Boolean

// Setでもunchecked warningを出されました
scala> def isIntSet(x:Any) = x match {
     |   case x:Set[Int] => true
     |   case _ => false
     | }
<console>:5: warning: non variable type-argument Int in type pattern is unchecked since it is eliminated by erasure
         case x:Set[Int] => true
                ^
isIntSet: (Any)Boolean


なお、配列は型消去の原則の例外らしいのできっちりチェックしてくれるみたいでデス

def isStringArray(x:Any) = x match {
  case x:Array[String] => "yes"
  case _ => "no"
}

//// 実行結果デス

// 文字列要素配列を判定できました
scala> val as = Array("Hello", "World")
as: Array[java.lang.String] = Array(Hello, World)

scala> isStringArray(as)               
res6: java.lang.String = yes

// 文字列要素配列以外もきちんと判定できました
scala> val ai = Array(1, 2)            
ai: Array[Int] = Array(1, 2)

scala> isStringArray(ai)   
res7: java.lang.String = no

ちなみに上記isStringArrayに直接配列をぶち込むと判定してくれないのはなんでだろう?オブジェクト生成タイミングとかなのかな?教えてエライ人ヽ( ・∀・)ノ

// なんか上手く判定してくれないのです(´;ω;`)
scala> isStringArray(Array("Hello", "World"))
res2: java.lang.String = no
変数の束縛

単純な値だけではなくて、パターンそのものを変数に格納することが出来るみたいです。


これは<変数名>@[パターン]という記法を使う束縛パターンというヤツらしんですが…まあサンプルやってみますかね

//// サンプル用のクラスです(前回使ったヤツ)
// 抽象基底クラス
abstract class Expr
/ 数値定義クラス
case class Number(num:Double) extends Expr
// 単項演算クラス
case class UnOp(opearator:String, arg:Expr) extends Expr

////  束縛パターンマッチです
// 絶対値演算2回を1回に置き換える処理です
def test(expr:Expr) = expr match{
  // 二回目のabs算術式を変数eで置き換えますよ
  case UnOp("abs", e @ UnOp("abs", _)) => e
  case _ =>
}

// 実行してみますよ
scala> test(UnOp("abs", UnOp("abs", Number(1))))
// eで置き換えたUnOp("abs", Number(1))が返されました
res9: Any = UnOp(abs,Number(1.0))

束縛したパターンを無事返すことができました(`・ω・´)

いじょー

とりあえずパターンに関するアレやコレをやりましたよー、次回はパターンガードやパターンシールドです。なんだかロボット格闘みたいな名前ですが…が、頑張ります

あ、夏休み進行なのでしばらく不定期になりますよー