前回は、Optionに値が格納されているケースといないケースの両方に対応するイディオムでした。今回は、これを拡張してOptionに格納されている値が行おうとしている処理に対して無効だったケースを追加します。
前回の演算は以下の表の示す演算でしたが、これを拡張して:
条件 | 結果 |
---|---|
Option[A]がSome[A] | Some[B] |
Option[A]がNone | None |
条件 | 結果 |
---|---|
Option[A]に有効な値が入っている | Some[B] |
Option[A]に無効な値が入っている | None |
Option[A]がNone | None |
以下では、引き続きOption[Int]からOption[String]への変換を例に考えてみます。ただし、Intは0以上のものが有効という条件を追加します。
Optionに入っているIntが0以上の場合、Some[String]が処理結果となります。一方、0未満の場合は無効となりNoneが処理結果となります。
(分類の基準)
Java風
if式でOption#isDefinedを使ってOptionが有効であることを確認後、さらにif式で値が0以上であることを判定します。このプログラムにあるようにa.get
を二度行なうか、a.get
の値を変数に覚えておかなくてはいけません。def f(a: Option[Int]): Option[String] = { if (a.isDefined) { if (a.get >= 0) Some(a.get.toString) else None } else { None } }
Scala風
match式を使うと以下のようになります。a.get
の二回判定(または a.get
の値を変数に記憶)が必要なくなるのでその点はすっきりしています。とはいえ「Noneの場合はNone」の処理を記述するのはScala的には悔しい感じ。def f(a: Option[Int]): Option[String] = { a match { case Some(b) => if (b >= 0) Some(b.toString) else None case None => None } }
Scala
Scala的なコーディングでは、withFilterメソッドを使って、「0以上」という条件に合わない値の場合に、Some[A]からNoneへの切替えを行います。def f(a: Option[Int]): Option[String] = { a.withFilter(_ >= 0).map(_.toString) }
Scalaz
Scalaz流のエレガントな書き方はないと思います。ノート
前回は以下の表の演算を行いましたが、今回はこれを拡張して:条件 | 結果 |
---|---|
Option[A]がSome | Some[B] |
Option[A]がNone | None |
条件 | 結果 |
---|---|
Option[A]に有効な値が入っている | Some[B] |
Option[A]に無効な値が入っている | None |
Option[A]がNone | None |
mapメソッドを使うと、成功の文脈における演算を記述することができましたが、成功の文脈を失敗の文脈に切り替えることはできませんでした。
withFilterメソッドは、指定した条件が真の場合はSome[A]を返し、偽の場合はNoneを返します。また、Noneを受け取った場合はそのままNoneを返します。つまりwithFilterメソッドを使うことで、成功の文脈を失敗の文脈に切り替えることができるわけです。
withFilterメソッドは以下の表の演算を行います。
条件 | 結果 |
---|---|
Option[A]に有効な値が入っている | Some[A] |
Option[A]に無効な値が入っている | None |
Option[A]がNone | None |
条件 | 結果 |
---|---|
Option[A]がSome[A] | Some[B] |
Option[A]がNone | None |
条件 | 結果 |
---|---|
Option[A]に有効な値が入っている | Some[B] |
Option[A]に無効な値が入っている | None |
Option[A]がNone | None |
withFilterとfilter
withFilterメソッドとほぼ同じ機能を持つfilterメソッドを使っても同じ結果が得られます。filterメソッドとwithFilterメソッドは、指定された条件でOptionの値をフィルタリングして、条件に合わない場合は成功の文脈(Some[A])から失敗の文脈(None)に切り替える点は同じです。
異なるのは、filterメソッドはOption[A]を返すのに対して、withFilterメソッドはWithFilterというオブジェクトを返す点です。WithFilterオブジェクトは、mapメソッドなどつないでいく場合に利用するオブジェクトです。
filterメソッドはメソッドチェインの最後に位置する場合に用いるのに対して、withFilterメソッドはメソッドチェインの内部にあり後続に別のメソッドがつながれる場合に使用します。メソッドチェインの最後に位置する場合は、Option[A]のオブジェクトを生成してこれを返すことになりますが、その後にすぐにmapメソッドなどがつながれる場合は、このOption[A]の生成が無駄になります。このような無駄を省くために用意されているのがwithFilterメソッドです。
filterメソッドとwithFilterメソッドの使い分けがよく分からない場合は、filterメソッドを使っておくと安全です。ただし、若干ですが性能的に不利になります。
filterメソッドとwithFilterメソッドには以上のような機能差があるため、Optionの値をフィルタした後にmapメソッドなどをつないでいく場合は、filterメソッドより高速に動作するwithFilterメソッドを使用するのがテクニックになっています。このためイディオムではwithFilterメソッドを採用しました。
collectメソッドとflatMapメソッド
Optionでは、filter/withFilterと同じような使い方ができるメソッドとしてcollect、filterNotが提供されています。filterNotメソッドはfilterメソッドと条件式が反対に作用するものです。
collectメソッドは部分関数を用いて、Optionの値を変換しながら、成功の文脈と失敗の文脈の切り替えもできるという優れもののメソッドです。
また、今回のイディオムはより汎用性の高いメソッドであるflatMapを用いて実現することもできます。
collectメソッドとflatMapメソッドを使ったイディオムについて、続けて取り上げていきます。
諸元
- Scala 2.9.1
- Scalaz 6.0.3