2012年7月5日木曜日

Scala Tips / Option (12) - Monoid

ScalazのOptionはMonoidとしても定義されています。

Scalazの型クラス」で説明したように型クラスMonoidは型クラスZeroと型クラスSemigroupを継承しています。

Semigroup

Monoidの性質のうちSemigroupが提供しているのは演算子|+|によるモノイド演算です。

Optionは、格納するオブジェクトがMonoidである場合、Monoidとして動作します。

両方のOptionがSomeの場合、格納されているMonoid同士に演算子|+|で演算した結果がSomeに格納されます。

scala> 1.some |+| 2.some
res10: Option[Int] = Some(3)

どちらか一方がNoneの場合は、Someの方が演算子|+|の演算結果となります。

scala> 1.some |+| none
res11: Option[Int] = Some(1)

scala> none |+| 2.some
res12: Option[Int] = Some(2)

両方共Noneの場合はNoneになります。

scala> none[Int] |+| none[Int]
res14: Option[Int] = None

Zero

Monoidはmzero関数で単位元(零元)を生成することができます。

Optionの場合は以下になります。

scala> mzero[Option[Int]]
res19: Option[Int] = None

Option[Int]の単位元としては、「Some(0)」が返ってきた方がよい場合もあると思いますが、現実装ではNoneが返ります。

使ってみる

OptionがMonoidであることを利用すると、以下のように直接sumrメソッドを適用することができます。

scala> List(1.some, 2.some, 3.some, 4.some).sumr
res32: Option[Int] = Some(10)

scala> List(1.some, 2.some, none, 4.some).sumr
res20: Option[Int] = Some(7)

OptionのMonoidの場合はOptionのコンテナ側の畳込みがOR演算的な処理になるので、Noneは無視されてSomeのみが畳み込まれます。

sequence

比較のためにsequenceメソッドを使ってみましょう。

scala> List(1.some, 2.some, 3.some, 4.some).sequence[Option, Int].map(_.sum)
res28: Option[Int] = Some(10)

scala> List(1.some, 2.some, none, 4.some).sequence[Option, Int].map(_.sum)
res27: Option[Int] = None

この場合は、1つでもNoneがあると結果もNoneになります。Optionのコンテナ側の畳込みがAND演算的な処理になります。

Applicative

Applicative Functorとして畳込む場合も、sequenceメソッドと同様にAND演算になります。

scala> List(1.some, 2.some, 3.some, 4.some).foldRight(0.some) { (x, a) => (x |@| a)(_ + _)}
res34: Option[Int] = Some(10)

scala> List(1.some, 2.some, none, 4.some).foldRight(0.some) { (x, a) => (x |@| a)(_ + _)}
res29: Option[Int] = None
mzeroは注意が必要

OptionをMonoidとして畳み込む時に注意が必要なのはmzero関数の結果がNoneになってしまう点です。このため、以下のようにApplicativeを使った畳込みの初期値に指定すると、畳込み結果が必ずNoneになってしまいます。

scala> List(1.some, 2.some, 3.some, 4.some).foldRight(mzero[Option[Int]]) { (x, a) => (x |@| a)(_ |+| _)}
res31: Option[Int] = None

scala> List(1.some, 2.some, none, 4.some).foldRight(mzero[Option[Int]]) { (x, a) => (x |@| a)(_ |+| _)}
res30: Option[Int] = None

ノート

Optionのようなコンテナは、畳込みのコンテナ側の演算としてOR演算とAND演算の選択肢があります。

OptionをMonoidとして畳み込むとOR演算、sequenceやApplicative Functorを使うとAND演算になることが分かりました。この振舞いの違いは、用途別のイディオムとして活用できますね。

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

0 件のコメント:

コメントを投稿