2012年1月27日金曜日

Scala Tips / Option (3)

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

Optionの値を取り出すのは、Option#getメソッドを使うかパターンマッチングでSome(x)とするのが普通ですが、イディオムとして整理する場合は、適材適所という切り口が重要になるので、もう少し大きな粒度のシーケンスでまとめておきたいところです。

Optionから値を取り出す処理は以下の2つのコーディングパターンのいずれかの構成要素として使われることが多いでしょう。それぞれのケースについて考えていきます。

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

まず、「Option[A]からOption[B]に変換」のイディオムです。Option[A]からOption[B]に変換するケースで注意が必要なのは、Option[A]がSome[A]ではなくNoneだったケースです。この場合は、AからBへの変換処理は行わずNoneにしなければなりません。これを表にまとめると以下のものになります。

条件結果
Option[A]がSome[A]Some[B]
Option[A]がNoneNone

Optionから値を取るイディオムでは、Noneのケースも包含した形にしておく必要があるというわけです。

以下では、Option[Int]からOption[String]への変換を例に考えてみます。

(分類の基準)

Java風

if式でOption#isDefinedを使って値の有無を判定します。Option#isDefinedとOption#getが泣き別れになるので、あまりよい感触ではありません。

def f(a: Option[Int]): Option[String] = {
  if (a.isDefined) Some(a.get.toString)
  else None
}

Scala風

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

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

Scala

Optionの処理にmapメソッドを使うのがScala的なコーディング。mapメソッド内に処理を記述します。(処理を行う関数を引数に指定します。)Noneの場合にはNoneになる、という処理はmapメソッド内で自動的に行ってくれるのがミソです。

def f(a: Option[Int]): Option[String] = {
  a.map(_.toString)
}

Scalaz

Scalaz流のエレガントな書き方はないと思います。

ノート

Optionの心は「成功/失敗の計算文脈」にあります。成功または失敗の状況を文脈として持ちまわるモナドという意味です。ボクが現時点で理解しているOptionの意味ですが、この言葉にピンと来ない場合もあまり気にする必要はありません。

実用的には、この抽象的な概念が具体的にどう役に立つのかという点が重要です。具体的に役に立つコーディングパターン、すなわちイディオムを洗いだしてマスターしておけばよいでしょう。

「成功/失敗の計算文脈」が具体的に何を指すのかという点について、前述した以下のmapメソッドを例に考えてみましょう。

def f(a: Option[Int]): Option[String] = {
  a.map(_.toString)
}

Option#mapメソッドは関数を引数に取りますが、成功の文脈(すなわちSome[Int]の場合)では関数を実行した結果(String)を再度Someに詰め直したもの(Some[String])を返却値とします。Someに詰め直すということは値を更新しつつ、成功の文脈を引き継ぐということです。

一方、失敗の文脈(すなわちNoneの場合)は何もせず自動的にNoneを返却値とします。つまり、失敗の文脈の処理についてはプログラマは直接指定する必要はなく、Option側で自動的に行ってくれるということです。失敗の文脈が自動的に引き継がれる点は、Java風(if式を使う場合)やScala風(match式を使う場合)と比べてみると違いが明確です。

プログラマの関心が低い方の文脈である失敗の文脈が、Optionが内包しているロジックにより自動的に引き継がれるのがOptionの旨みで、Optionの実現技術であるモナドの威力です。

2 件のコメント:

  1. エレガントかどうかは各人の判断に任せますが scalaz way としては ApplicativeBuilder とかになると思います。

    for { x <- optX; y <- optY } yield f(x, y)

    が、

    (optX |@| optY)(f)

    みたいに書けるみたいな。

    返信削除
  2. Applicative Functor版は今回の趣旨からはちょっとはずれるので、別途取り上げる予定ですw

    返信削除