2012年2月7日火曜日

Scala Tips / Option (9)

Optionから値を取り出すイディオムです。

Optionから値を取り出す処理として以下の2つのコーディングパターンを挙げました。

  • Option[A]からOption[B]に変換
  • Option[A]からBに変換

前回は、後者のOption[A]からBへの変換について、基本的なイディオムを紹介しました。

条件結果演算
Option[A]がSome[A]BAからBを計算
Option[A]がNoneBデフォルト値

今回は、Option[A]がNoneの場合だけでなく、Some[A]の値が条件を満たさない場合も、デフォルト値を返すようにします。

条件結果演算
Option[A]に有効な値が入っているBAからBを計算
Option[A]に無効な値が入っているBデフォルト値
Option[A]がNoneBデフォルト値

以下では、Option[Int]からStringへの変換を例に考えてみます。ただし、Intは0以上のものが有効という条件を追加します。Optionに入っているIntが0以上の場合、Stringが処理結果となります。一方、0未満の場合は無効となり空文字列「""」が処理結果となります。またOption[A]がNoneの場合も空文字列「""」が処理結果となります。

Java風

if式でOption#isDefinedを使って値の有無を判定します。isDefinedAtメソッドが一回、getメソッドが二回が泣き別れになってしまいます。

def f(a: Option[Int]): String = {
  if (a.isDefined && a.get >= 0) a.get.toString
  else ""
}

Scala風

match式を使うと以下のようになります。こちらの方が綺麗ですね。

def f(a: Option[Int]): String = {
  a match {
    case Some(b) if (b >= 0) => b.toString
    case _ => ""
  }
}

Scala

Option(4)のwithFilterメソッド、mapメソッドとOption のgetOrElseメソッドの合わせ技で実現できます。

def f(a: Option[Int]): String = {
  a.withFilter(_ >= 0).map(b.toString) getOrElse ""
}

Scalaz

Scalazの場合はOption(7)の技法にOption(4)のwithFilterメソッドまたはfilterメソッドを組合わせて、実現できます。

注意点としては、cataメソッドとfoldメソッドの場合はwithFilterではなくてfilterを使う必要があります。cataメソッドとfoldメソッドはScalazが拡張したメソッドで、ScalaのWithFilterオブジェクトは扱えないからです。

def f(a: Option[Int]): String = {
  a.withFilter(_ >= 0).map(_.toString) | ""
}
def f(a: Option[Int]): String = {
  a.filter(_ >= 0).cata(_.toString, "")
}
def f(a: Option[Int]): String = {
  a.filter(_ >= 0).fold(_.toString, "")
}

def f(a: Option[Int]): String = {
  a.withFilter(_ >= 0).map(_.toString) orZero
}
def f(a: Option[Int]): String = {
  ~a.withFilter(_ >= 0).map(_.toString)
}

ノート

今回はOption(4)のwithFilterメソッドを組み合わせてみましたが、Option(5)のcollectメソッド、Option(6)のflatMapメソッドでも同じように実現できます。

要するに、Option上で(つまり成功失敗の計算文脈上で)計算を続け、最後にcataメソッドなどでOptionから値を取り出すというメカニズムです。

諸元

  • Scala 2.9.1
  • Scalaz 6.0.3

0 件のコメント:

コメントを投稿