かとじゅんの技術日誌

技術の話をするところ

Scalazを使ってMaybeモナドを自作してみる(後編)

はい。

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は使えますね。