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。