2012年2月2日木曜日

Scala Tips / Option (6)

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

以下の表が示す演算について考えています。

条件結果
Option[A]に有効な値が入っているSome[B]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone

withFilterバージョン、collectバージョンに続いて、flatMapバージョンです。

引き続きIntは0以上のものが有効という条件付きのOption[Int]からOption[String]へ変換を例に考えてみます。

(分類の基準)

Java風

Option(4)と同じです。

Scala風

Option(4)と同じです。

Scala

Option[Int]のflatMapメソッドは、Option[Int]の格納する値であるIntを引数に取り、この場合はOption[String]を返す関数を実行します。Option(3)で取り上げた、mapメソッドの場合はIntを引数に取りStringを返す関数を実行し、その結果をmapメソッド側でOptionに詰めなおしますが、flatMapの場合は、StringをOptionに詰め直す作業もアプリケーション側の関数で行います。

def f(a: Option[Int]): Option[String] = {
  a.flatMap { b =>
    if (b >= 0) Some(b.toString)
    else None
  }
}

Scalaz

Scalazの場合もflatMapメソッドを使いますが、flatMapの中のロジックをより簡潔に記述することができます。

def f(a: Option[Int]): Option[String] = {
  a.flatMap(b => (b >= 0).option(b.toString))
}

Scalazでは、Booleanにoptionメソッドを拡張しています。optionメソッドでは、Booleanが真の場合、指定した関数が実行され、その結果をSomeに詰めたものが返されます。一方、偽の場合はNoneが返されます。

(b >= 0).option(b.toString)は、bの値が0以上の場合にbをStringにしたものをSome[String]に詰めて返し、そうでない場合はNoneを返します。

Scalazでは、このように式を簡潔に記述できる便利なメソッドが多数追加されています。

ノート

今回の用途では、Optionのfilter/withFilterメソッドやcollectメソッドで十分に要件を満たせるので、イディオムとしてはこの2つだけもよかったのですが、プリケーションの意志で成功の文脈を失敗の文脈に切り替えるということを、もっと直接的な形で行う手法をマスターしておかないと応用が効かないので、flatMapメソッドも取り上げました。

以下の表の演算において:

条件結果
Option[A]に有効な値が入っているSome[B]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone

「Option[A]に無効な値が入っている」部分が、アプリケーションの意志で成功の文脈を失敗の文脈に切り替えるところになります。

filter/withFilterメソッドやcollectメソッドは自動的にこの切り替えを行ってくれるわけですが、それぞれのメソッドの機能ははっきり決まっていて、提供された機能以外の用途に使うのは得策ではありません。

そういった汎用目的で成功の文脈を失敗の文脈に切り替える処理を行うのがflatMapメソッドです。

mapメソッドを使うと、成功の文脈における演算を記述することができましたが、成功の文脈を失敗の文脈に切り替えることはできませんでした。flatMapメソッドは、この切り替えを記述するためのメソッドというわけです。

モナドは計算文脈をカプセル化して扱う技術と考えることができます。(モナドの一種であるOptionは成功/失敗の文脈をカプセル化していました。)この計算文脈の操作に色々な手法が存在するわけですが、その中軸となるのがflatMapメソッドです。

flatMapメソッドを使いこなせるようになるとScalaプログラミングの幅がぐんと広がります。

filter, collect, flatMapの使い分け

前回はfilterメソッドとcollectメソッドの使い分けについて説明しましたが、これにflatMapが加わりました。

filterメソッドやcollectメソッドは、使い方の形が決まっているので、これにぴったりハマるケースはfilterメソッドやcollectメソッドを使えばよいでしょう。

あまりぴったりはまらないケース、判定ロジックと生成ロジックが入り乱れているような場合に、flatMapメソッドを使うことになります。

諸元

  • Scala 2.9.1
  • Scalaz 6.0.3

0 件のコメント:

コメントを投稿