かとじゅんの技術日誌

技術の話をするところ

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

Play or Scala Advent Calendar 2012の 12/21日の記事です。

モナドを理解するために、Haskellでモナドを作ってみたのですが、Scalaz*1でもMaybeモナドを作ってみようと思い試した結果を報告します。

MyOptionを定義する

例のごとくMaybe型を定義します。 Haskellより冗長だけどまぁこんな感じで書けます。

sealed trait MyOption[+T]
case object MyNone extends MyOption[Nothing]
case class MySome[T](value: T) extends MyOption[T]

Functor型クラスの実装

Scalazでは次のように実装します。implicit objectですね。

object MyOption {
  implicit object Functor extends Functor[MyOption] {
    def map[A, B](fa: MyOption[A])(f: A => B): MyOption[B] = {
      fa match {
        case MySome(v) => MySome(f(v))
        case MyNone => MyNone
      }
    }
  }
}

利用する時はこんな感じ。*2 op1にmapメソッドがないけど呼べるのは、FunctorOpsのおかげですね*3

object Main extends App {
  val op1: MyOption[Int] = MySome(1)
  println(op1.map(_ + 1))
}

Applicative型クラスの実装

Functor型クラスの強化版である、Applicative*4型クラスの実装です。

強化版なのでFunctorの機能はそのまま使えます。pointメソッドはaを保持するMySomeを返します。 apメソッドはちょっと難しいですが、MyOptionに通常の値と、MyOptionに関数が格納されていて、その関数に値を適用した結果をMySomeに格納します。

object MyOption {
  implicit object Applicative extends Applicative[MyOption] {

    def point[A](a: => A): MyOption[A] = MySome(a)

    def ap[A, B](fa: => MyOption[A])(f: => MyOption[A => B]): MyOption[B] = {
      (f, fa) match {
        case (MySome(l), MySome(r)) => MySome(l(r))
        case _ => MyNone
      }
    }
  }
}

利用例を見た方が早いですね。op1は値が格納されていて、op2は引数に1を加算して戻り値で返す関数が格納されています。 op3は二つの引数を加算する関数ですね。 r1の例では、<*>を使って関数と値を適用しています。MySomeから値を取り出すことなく。これは便利!

r2の例では値を格納するMySome同士をop3に適用して加算しています。

他にもメソッドあるので試してみるといいですね!

object Main extends App {
  val op1: MyOption[Int] = MySome(1)
  val op2: MyOption[Int => Int] = MySome((_:Int) + 1)
  val op3 = (_:Int) + (_:Int)

  println(op1.map(_ + 1))

  val r1 = op1 <*> op2
  println(r1) // MySome(2)

  val r2 = (op1 <**> op1)(op3)
  println(r2) // MySome(2)

}

Applicativeにはmapメソッドの実装はないのですが、次のようにpointとapで実装できてしまうようです。

override def map[A, B](fa: F[A])(f: A => B): F[B] =
    ap(fa)(point(f))

ということで、Scalazを使えばHaskellの型クラスの種類を一通り実現できそうです。 後編でMonoidとMonadを実装します*5

*1:Scalaz7です

*2:型アノテーションを記述しないとコンパイルエラーになるのね…

*3:気になる人はpackage objectであるsyntaxあたり参照。ここも参考にするとよい → http://d.hatena.ne.jp/xuwei/20121217/1355687398

*4:Applicativeと略していますが、Applicative Functorが正式名のようです

*5:後編はScalaz Advent Calendarで、22日でお願い!