はい。
Scalaz Advent Calendar 2012 12/22 です。
前回に引き続き、MyOptionのためのMonoidとMonadを実装してみます。
Monoidの実装
Monoidはtrait Monoid[F]と定義されているので、今回はMyOptionのInt型の実装を定義します。 コードは次のとおり。
実装すべきメソッドはzeroとappendです。zeroは単位元というらしいです。加法では0が単位元らしい。ググればわかりますw
zeroにはMyNoneを指定します。このappendは二つのMyOptionがMySomeの場合だけ、それぞれの値を加算しMySomeでラップし返します。MyNoneの場合はMyNoneを返します。 実はMonoidはSemigroupを継承していて、appendはSemigroupのメソッドです。zero自体はMonoidで定義されているメソッドです。
object MyOption { implicit object IntMonoid extends Monoid[MyOption[Int]] { def zero = MyNone def append(f1: MyOption[Int], f2: => MyOption[Int]):MyOption[Int] = (f1, f2) match { case (MySome(l), MySome(r)) => MySome(l+r) case _ => MyNone } } }
SemigroupOpsには|+|というメソッドが定義されています。appendを呼び出してくれます。次のコードを実行するとMySome(2)が得られます。
object Main extends App { val op1: MyOption[Int] = MySome(1) val r1 = op1 |+| op1 println(r1) // MySome(2) }
MonoidSyntaxも試してみたいので、isMZeroメソッドを使ってみます。isMZeroのシグニチャを見るとEqual型クラスが必要だとわかるので、そのための実装を次のとおりに定義します。
object MyOption { implicit object IntEqual extends Equal[MyOption[Int]] { def equal(a1: MyOption[Int], a2: MyOption[Int]) = { (a1, a2) match { case (MySome(l), MySome(r)) => l == r case _ => false } } } // IntMonoid ... }
次のコードを実行するとMySomeで値を持っているわけですが、falseになります。
object Main extends App { val op1: MyOption[Int] = MySome(1) val b = op1.isMZero println(b) // false }
Monadの実装
Monadの実装は次の通り。
ソース見ると、MonadはApplicativeとBindを継承していますね。ということで、Applicative#pointと、Bind#bindを実装します。 pointはaをMySomeでラップするだけ。bindの場合はfにMySomeの値を適用、MyNoneの場合MyNoneを返す。やってることはむちゃくちゃ簡単。
object MyOption { implicit object Monad extends Monad[MyOption] { def point[A](a: => A): MyOption[A] = MySome(a) def bind[A, B](fa: MyOption[A])(f: A => MyOption[B]): MyOption[B] = fa match { case MySome(v) => f(v) case MyNone => MyNone } } }
利用例は次のとおり。
BindSyntaxにはflatMapメソッドがあります。
Haskellの>>=
相当のメソッドも使えますね。
object Main extends App { val op1: MyOption[Int] = MySome(1) val f1 = op1.flatMap{x => MySome(x*2)}.flatMap{x => MySome(x+2)} println(f1) // MySome(4) val f2 = op1 >>= {x => MySome(x*2)} >>= {x => MySome(x+2)} println(f2) // MySome(4) }
モナド言えばdo式ですが、Scalaではfor式を使います。 上記とは違う処理内容ですが、MySomeが二重になっている場合の計算も次のようにかけます。
val op2: MyOption[MyOption[Int]] = MySome(MySome(1)) val f3 = for{x <- op2 y <- x} yield y*2 println(f3) // MySome(2)
というわけで、かなり搔い摘んで説明したけど、HaskellでやってたことがScalazでもできますね。 Scalaで、カテゴリに対応する型クラスを実装するなら、Scalazは使えますね。