2014年6月13日金曜日

実用Scalaz/Option + Monoid

OptionはScalaプログラミングのキーパーツであり、Optionの捌き方の巧拙がScalaプログラミングの効率に直結します。

これと同様にMonoidはScalazプログラミングのキーパーツということができるかと思います。Monoidの捌き方の巧拙がScalazプログラミング、さらにはMonadicプログラミングの効率に直結することになるでしょう。

そして、ScalazのおいてOptionは代表的なMonoidです。つまり、OptionをMonoidとして捌いていく技法はScalazプログラミング、Monadicプログラミングにおいて最重要のテクニックといえるわけです。

というととても難しい技術のように見えますが、(背景の数学的な理論は別として)使い方はとても簡単で、さらに頻出のユースケースをカバーする非常に便利なイディオムです。

準備

説明の準備に以下の関数を定義します。

scala> def a: Option[Int] = Some(100)
def a: Option[Int] = Some(100)
a: Option[Int]

scala> def b: Option[Int] = None
def b: Option[Int] = None
b: Option[Int]

よくある処理

プログラムを書いているとよく出てくるのが以下のような処理です。

def plus(lhs: Option[Int], rhs: Option[Int]): Option[Int] = {
  lhsの中身とrhsの中身を加算する
  ただしlhsまたはrhsのどちらか一方がNoneの場合はSomeの方の値を使用する
  両方がNoneの場合はNoneにする
}

よくある間違い

この処理を書く場合、Optionを使ってMonadicに処理すると行けそうに思えます。そこで、次のような処理を書いたとしましょう。

def plus(lhs: Option[Int], rhs: Option[Int]): Option[Int] = {
d lhs.flatMap(x => rhs.map(y => x + y))
}

これは、for式を使って以下のように書くこともできます。

def plus(lhs: Option[Int], rhs: Option[Int]): Option[Int] = {
  for (x <- lhs; y <- rhs) yield x + y
}

さて、この結果ですが、以下のようになります。

scala> plus(a, a)
res1: Option[Int] = Some(200)

scala> plus(a, b)
res3: Option[Int] = None

scala> plus(b, a)
res4: Option[Int] = None

scala> plus(b, b)
res2: Option[Int] = None

Monadicに処理した場合は、残念ながらいずれかのOptionがNoneだった場合に、結果もNoneになってしまうわけです。このような処理が適切なケースも多いわけですが、前述の「plus関数」の定義とは異なるのでこの実装は使えません。

Match式

「plus関数」をmatch式を使って実装すると以下のようになります。

def plus(lhs: Option[Int], rhs: Option[Int]): Option[Int] = {
  (lhs, rhs) match {
    case (Some(l), Some(r)) => Some(l + r)
    case (Some(l), None) => Some(l)
    case (None, Some(r)) => Some(r)
    case (None, None) => None
  }
}

このようなOptionの組み合わせごとに処理を分けるmatch式はScalaプログラミングをしているとよく出てくるのではないでしょうか?しかも、かなり野暮ったい感じなのでこれを簡単に書けると好都合です。

Monoid

前出のmatch式による処理をScalazのMonoidで書くと以下になります。複雑なmatch式を演算子「|+|」のみで記述できています。

ScalazではOptionのMonoid演算として、前述のmatch式相当の演算を割り当てているわけですね。

def plus(lhs: Option[Int], rhs: Option[Int]): Option[Int] = {
  lhs |+| rhs
}

この「plus」関数の実行結果は以下になります。

scala> plus(a, a)
res9: Option[Int] = Some(200)

scala> plus(a, b)
res10: Option[Int] = Some(100)

scala> plus(b, a)
res11: Option[Int] = Some(100)

scala> plus(b, b)
res13: Option[Int] = None

おさらい

おさらいの意味でOptionに対してMonoidの演算子「|+|」を適用して演算を行った結果を以下に示します。

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

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

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

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

参考

Option Index

Optionに関する2012年ごろのブログのまとめです。

Monoid Index

Monoidに関する2012年ごろのブログのまとめです。

諸元

  • Scala 2.10.4
  • Scalaz 7.0.6

0 件のコメント:

コメントを投稿