2012年2月1日水曜日

Scala Tips / Option (5)

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

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

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

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

前回はwithFilterメソッドとmapメソッドを組合せてこの表の演算を実現しましたが、これを一発で行うメソッドがあります。これがcollectメソッドです。

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

(分類の基準)

Java風

Option(4)と同じです。

Scala風

Option(4)と同じです。

Scala

Option#collectメソッドにケースシーケンスを用いて部分関数(PartialFunction)を指定します。

指定した部分関数は、「指定されたInt値が0以上の場合には値をStringに変換する」というものです。指定されたInt値が0以上でない場合には、この部分関数は適用されません。

以下のプログラムでは、Option[Int]がSome[Int]の場合、部分関数が適用可能か(つまりInt値が0以上か)を検査し、適用可能の場合に部分関数を評価することでInt値をStringに変換します。collectメソッドはさらにStringをSome[String]に詰めなおして返します。また、Option[Int]がNoneの場合、あるいはSome[Int]だけど部分関数が適用可能でなかった場合はNoneを返します。

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

Scalaz

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

ノート

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

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

withFilterメソッドとmapメソッドを組合せて実現していた処理をcollectメソッド一発で実現できました。

collectメソッドは非常に強力ですが、その力の源となっているのがPartialFunctionという関数オブジェクトです。部分関数と呼ぶこともできます。collectメソッドはPartialFunctionを引数に取ります。

関数の引数に指定された型の一部の値のみを評価できる関数が部分関数です。評価できない値に対しては関数は未定義となります。

ケースシーケンスや部分関数、PartialFunctionという用語は見慣れないかもしれませんが、プログラムをみれば、やろうとしていることは一目瞭然だと思います。まずは、match式の後ろ半分case句の集りがケースシーケンスで、ケースシーケンスはPartialFunctionという関数オブジェクトに落とし込まれる、と覚えておいてください。(使い方に慣れてきたら文法書を確認して正確な定義を理解しておくとよいでしょう。)

PartialFunctionをScalaプログラムのリテラルとして記述する方法がケースシーケンスです。ケースシーケンスはmatch式からキーワードmatchを除いた後ろ半分の形です。今回のプログラムの以下の部分ですね。(アスタリスクは無視)

******** {
    case b if b >= 0 => b.toString
  }

処理の意味はmatch式のcase句と同様です。指定されたInt値を変数bにバインドし、このbの値が0以上の場合は、Int値をStringに変換します。0以上の値でない場合は、この部分関数は未定義となります。部分関数が未定義の場合、Option#collectメソッドはNoneを返します。

PartialFunctionの主要なメソッドは以下の2つです。

メソッド機能
isDefinedAt指定された値が関数の引数として有効化を判定
apply関数の評価

通常の関数オブジェクト(Function1など)はapplyメソッドは持っていますが、isDefinedAtは持っていません。

isDefinedAtメソッドは指定された値が関数に取って有効かどうかを判定するメソッドです。collectメソッドは、このisDefinedAtメソッドを使用して、現在扱っている値に対して部分関数が未定義がどうかを判定しているわけです。

前述のケースシーケンスの場合、case句中の「if b >= 0」がisDefinedAtメソッドの実装に、「b.toString」がapplyメソッドの実装になるわけです。ケースシーケンスは、こういったPartialFunctionオブジェクト定義の文法糖衣というわけですね。

PartialFunctionはScalaプログラミングの様々な場所に現れ、非常に重要な役割を担います。これからも色々なイディオムに登場すると思います。

filter, collectの使い分け

今回の用途では、filterメソッドとcollectメソッドはどちらも似たような効果を得ることができます。好みの方を使うとよいでしょう。

filterメソッドの方が機能が絞り込まれている分、プログラムがやりたいことの意図ははっきりすると思います。

ボク自身のプログラミングでは、多分collectメソッドの方を使うと思います。ちょっとだけ性能が速そうなので。

filterメソッドとcollectメソッドで明らかにcollectメソッドが有用なケースもあるのですが、これは別の機会に取り上げたいと思います。

諸元

  • Scala 2.9.1
  • Scalaz 6.0.3

0 件のコメント:

コメントを投稿