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


Scalaコップ本の15章の続きをやっていきますよー、15章はパターンマッチに関するアレやコレです。今回はOption型についてやります

とりあえず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

Option型

値の存在判定時に他の言語なんかではNullで判定したりするのですが、チェックミスでNullPointerExceptionが投げられまくって、リアル( ´∀`)に<ぬるぽ、ヽ( ・∀・)ノ┌┛ガッΣ(ノ`Д´)ノのコンボになってしまいマス

そこでScalaはOption型という標準型を取り入れてこれに対処しますよ!って話です。

Option型の2形態

Option型は次のような2つの形を持っているそうです

  • 実際の値を持っているSome(△)
  • 値が無いことを表すNone

具体的な動きを確認したいのでOption型を返すMapのgetメソッドを使ってサンプルを動かしみましょうかねー

// Mapを定義しますよ
scala> val testMap = Map("hoge" -> 1, "moge" -> 2, "huga" -> 3)
testMap: scala.collection.immutable.Map[java.lang.String,Int] = Map(hoge -> 1, moge -> 2, huga -> 3)


// 値が存在するOption型Some(◇)が返ってきますね
scala> testMap.get("hoge")
res0: Option[Int] = Some(1)

// 値が存在しないオプション型Noneが返ってきます
scala> testMap.get("hige")
res1: Option[Int] = None
Option型でパターンマッチ

返ってきた値ありバージョンのOption型は値を分解して取り出すことが出来るみたいです。よく使われるのはパターンマッチらしいので、実際に動かしてみますよ

// サンプル用のMapを定義しますよ
val greetings = Map("hello" -> "scala", "hi" -> "python", "bye" -> "php")
// パターンマッチ処理です
def show(x:Option[String]) = x match {
  // 値ありOption型の場合は中身を
  case Some(s) => s
  // 価なしだったらunknownを返します
  case None => "unknown"
}

//// んじゃ試してみますよ
// 値ありバージョンです
scala> show(greetings.get("hello"))                                             
res4: String = scala
// 値なしバージョンです
scala> show(greetings.get("morning"))
res5: String = unknown

うん、値分解できましたねー

Option型の使いどころ

Option型を活用することで以下の2つのようなメリットがるみたいなので、渡される値がnullになる可能性がありそうな場合はOption型を使うのがよさそうですねー

  • Option型がわたってきた場合はnullの可能性がある
  • そもそもOption型(Optin[String])を普通の値(String)で使おうとすると型チェックエラーが出るのでチェックミスを防げる

うん、うまい具合に使えばかなりミスを減らせそう。でもパターンマッチ以外での使いどころってのは何処になるんだろう…まあそのうち出てくるかな(´・ω・`)

どこでもパターンを

パターンマッチ式以外でもパターンの利用ができるのデス。それがScalaです( ー`дー´)キリッってことで様々なパターン活用法を見ていきますよ

変数定義におけるパターン

var やvalの定義のときにパターンを使うことで複雑な代入を行うことが出来るみたいです。

下の例ではタプルの各要素を別の変数に代入しています

// タプルを定義
scala> val testTuple = (123, "abc")
testTuple: (Int, java.lang.String) = (123,abc)

// タプルの各要素をnumber, stringに代入
scala> val (number, string) = testTuple
number: Int = 123
string: java.lang.String = abc

Pythonでも似た様なことをやるけど、いちいち展開する必要がなくなるからコレが出来るのはかなり便利ですねー

このような分解代入(造語は)ケースクラスでも可能みたいですねー

// 二項演算を行う算術式を定義します
scala> val exp = new BinOp("*", Number(5), Number(1))
exp: BinOp = BinOp(*,Number(5.0),Number(1.0))

// 定義した算術式の各要素を分解して取得します
scala> val BinOp(op, left, right) = exp
op: String = *
left: Expr = Number(5.0)
right: Expr = Number(1.0)
部分関数としてのケースシーケンス

パターンマッチ用のケースシーケンス({ }の中身)は関数リテラルのあらゆるところで使えるらしいので、実際に関数に適用してみますよー

// 関数としてケースシーケンスを定義してやりました
val withDefault:Option[Int] => Int = {
  case Some(x) => x
  case None => 0
}

//// 実行してみますよ
// 値ありバージョンです
scala> withDefault(Some(10))  
res10: Int = 10
// 値なしバージョンです
scala> withDefault(None)    
res11: Int = 0

パッと見だとパターンマッチ式の変形のように見えるんですが、関数化してるところに意義がありそう…実際この機能はアクターライブラリで使うみたいです。アクターライブラリについては30章で詳しくやるみたいですが、アクターライブラリで使用するアクターコードにサラッと触れてみますかねー

アクターってオブジェクト間の通信定義(うろ覚え)だっけか?とりあえずサンプルのreactメソッドですよ

react {
  //パターンマッチを関数として利用してますね
  case(name:String, actor:Actor) => {
    actor ! getip(name)
    act()
  }
  case msg => {
    println("Unhandled message: " + msg)
    act()
  }
}

ついでにケースシーケンスの部分関数定義もやってみます。ちなみに部分関数は一部の引数を固定してそれ以外を_で指定したような関数でした。

例えば次のような2つ以上の要素を持つList[Int]をパラメータとして取って整数を返す部分関数として定義された若干問題ありありのケースを考えます。

val second: List[Int] => Int = {
  // Listの第1要素と第2要素から構成されるListかどうか、を部分関数で判定します
  case x :: y :: _ => y
}

//// コンパイラーに網羅的でないマッチと怒られるものの定義できます
<console>:4: warning: match is not exhaustive!
missing combination            Nil

       val second: List[Int] => Int = {
                                      ^

とりあえず動かして問題点を探ってみます

// まずは条件にあった(きちんとマッチする)パターンです
scala> second(List(1,2,3))
// きちんと第2要素が取り出されました(`・ω・´)     
res19: Int = 2

// 部分関数の条件に合わない値を渡しますよ
scala> second(List(1))    
// Matchエラーでまくりです
scala.MatchError: List(1)
	at $anonfun$1.apply(<console>:4)
	at $anonfun$1.apply(<console>:4)
	at .<init>(<console>:6)
	at .<clinit>(<console>)
	at RequestResult$.<init>(<console>:3)
	at RequestResult$.<clinit>(<console>)
	at RequestResult$result(<console>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAcces...

このようにパラメーターがケースの部分関数条件を満たさない(用意されたパターンにマッチしない)場合を防ぐためには、部分関数の持つisDefinedAtメソッドを使うのがいいみたいです。

手始めにコンパイラ様に対して部分関数操作をしてるぜ!宣言をします。上の例では整数リストをパラメータとして整数を生成する部分関数を扱っているので、PartialFunction[List[Int], Int]をを使って記述しますよー

// PartialFunction[List[Int], Int]を使って書き直しました
val second: PartialFunction[List[Int], Int]  = {
  case x :: y :: _ => y
}

んじゃisDefinedAtメソッドで判定します

// 条件を満たしているのでtrueを返しますよ
scala> second.isDefinedAt(List(1,2,3))
res23: Boolean = true
// 条件を満たさないのでfalseを返しますよ
scala> second.isDefinedAt(List(1))
res24: Boolean = false

きちんと出来ましたねー、ちなみに上のような{ case x :: y :: _ => y }という関数リテラルScalaコンパイラで次の用に変換されるみたいです

// パターンが2回変換されて実際の関数用と確認用に定義されます
new PartialFunction[List[Int], Int] {
  // 実際の関数の実装です
  def apply(xs:List[Int]) = xs match {
    case x :: y :: _ => y
  }
  // isDefinedAtを使った確認用の定義です
  def isDefinedAt(xs: List[Int]) = xs match {
    case x :: y :: _ => true
    case _ => false
  }
}

上記のような変換はPartialFunctionであればいつでも行われるっぽいっすね。宣言されなかったりFunction1で宣言された場合は”全関数”とかいうのに変換されるっぽいです...全関数は部分関数じゃないものって解釈で良いのかしら(´・ω・`)

コップ本いわく部分関数はランタイムエラー起きる余地があって、かつコンパイラーで防ぎきれんから基本的には全関数使え、でもisDefinedAtで必ずチェックするような環境(フレームワークとか)が整っている部分関数想定の状態だったら使えばいいよ( ー`дー´)キリッとのことです。アクターのreactサンプルなんかはそうらしいですね。

…ここまでやってみた部分関数パターンマッチはイマイチあやふやなところがあるので、使って行ってきっちり覚えないとダメな感じですねー

for式内のパターン

for式の中でもパターンが利用できますね、値の分解をしなくても済むので結構便利そう(`・ω・´)とりあえずサンプルやってみますかねー

// タプルを定義しますよ
scala> val greetings = List(("hello", "scala"), ("hi", "python"), ("bye" , "php"))
greetings: List[(java.lang.String, java.lang.String)] = List((hello,scala), (hi,python), (bye,php))

// ループの中で各要素を分解して格納して表示しますよ
scala> for ((greeting, language) <- greetings) println(greeting + ' ' + language)
hello scala
hi python
bye php

ちなみにパターンに当てはまらない値は捨てられるそうです

// Listを定義しますよ
scala> val results = List(Some("apple"), None, Some("MS")) 
results: List[Option[java.lang.String]] = List(Some(apple), None, Some(MS))

// Listを使ってループします
scala> for (Some(company) <- results) println(company)
// 条件に合わないNoneは捨てられましたー
apple
MS

うん、簡単なフィルターとしてもつかえそうですね(`・ω・´)

いじょー

ようやくパターンマッチの座学的なところが終わりですねー、次回はパターンマッチを使った具体例でサンプルライブラリをいじり倒しますよー