2012年1月31日火曜日

Scala Tips / Option (4)

Optionから値を取り出すイディオムです。
前回は、Optionに値が格納されているケースといないケースの両方に対応するイディオムでした。今回は、これを拡張してOptionに格納されている値が行おうとしている処理に対して無効だったケースを追加します。
前回の演算は以下の表の示す演算でしたが、これを拡張して:
条件結果
Option[A]がSome[A]Some[B]
Option[A]がNoneNone
以下の表が示す演算にします。
条件結果
Option[A]に有効な値が入っているSome[B]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone
「Option[A]に無効な値が入っている」のケースが加わっています。
以下では、引き続き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]がSomeSome[B]
Option[A]がNoneNone
以下の表の演算を行いました。
条件結果
Option[A]に有効な値が入っているSome[B]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone
「Option[A]に無効な値が入っている」のケースが加わっていますが、この追加が何を意味しているのかというと、成功の文脈だったものをアプリケーションの意思で失敗の文脈に変えることができるということです。
mapメソッドを使うと、成功の文脈における演算を記述することができましたが、成功の文脈を失敗の文脈に切り替えることはできませんでした。
withFilterメソッドは、指定した条件が真の場合はSome[A]を返し、偽の場合はNoneを返します。また、Noneを受け取った場合はそのままNoneを返します。つまりwithFilterメソッドを使うことで、成功の文脈を失敗の文脈に切り替えることができるわけです。
withFilterメソッドは以下の表の演算を行います。
条件結果
Option[A]に有効な値が入っているSome[A]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone
また、mapメソッドは以下の表の演算を行います。
条件結果
Option[A]がSome[A]Some[B]
Option[A]がNoneNone
この2つの表を連結すると以下の表になります。
条件結果
Option[A]に有効な値が入っているSome[B]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone
以上のようにwithFilterメソッドとmapメソッド連結することで、全体として期待した結果が得られます。
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

0 件のコメント:

コメントを投稿