2013年10月10日木曜日

Scalaでモナドを作成する

ScalaのEitherモナドが正直言って使い勝ってが悪いので、自分なりに使い勝手がよいモナドを作ってみました。

Eitherモナド

Eitherモナドのどういうところが使い勝手が悪いかと言うと、forとかでEitherモナドをつなげて使う時です。
例えば、次のようにEitherを4つ繋げるときです。

    for {
      a <- Right(1).right
      b <- Right(2).right
      c <- Left("error").right
      d <- Right(4).right
    } yield (a, b, c, d)

この結果は、Left("error")を返します。
ここで面倒くさいのが、Rightで繋げていきたいときは、.rightLeftで繋げていきたいときは、.leftメソッド呼び出さないといけないことです。僕がEitherモナドを使うときは、Rightに成功したときの値でLeftに失敗した時のメッセージなどを入れることがほとんどなので、.leftを使うことは、ほぼ有りません。.rightを省いて使いたいのです。
僕にとっては、次のように使えるモナドがあれば都合がいいです。

    for {
      a <- Good(1)
      b <- Good(2)
      c <- Bad("error")
      d <- Good(4)
    } yield (a, b, c, d)

こんなふうに、Goodのときは、どんどん繋がっていき、途中でBadがあったら、そこで止まるというようなモナドがあったら便利です。
そこで自分で都合の良いモナドを作ってみることにしました。

Resultモナド

自作モナドをResultモナドと呼ぶことにします。何かの「結果」を返す時に使うことが多いからです。

仕様概要

Resultサブクラスには、成功したときの結果を入れるGood、失敗した時の結果を入れる使うBadが必要でしょう。さらに、失敗したけど結果がないときに使えるEmptyもあったら便利です。

forで使うには、foreachfiltermapflatMapを定義する必要があります。

他のモナドにもあるような、getgetOrElseorElseexistsforallなどのメソッドも実装します。

型パラメータ

Resultは、2つの型パラーメータを取ります。それぞれ、GoodBadの保持する型です。Emptyが保持する型は、全ての型のサブクラスとして返す必要があるので、Nothingにします。このため、Resultの2つの型パラメータを共変にしなければなりません。

  abstract class Result[+A, +B]
  case class Good[+A, +B]( a: A ) extends Result[A, B]
  case class Bad[+A, +B]( b: B ) extends Result[A, B]
  case object Empty extends Result[Nothing, Nothing]

flatMapメソッド

flatMapは、Good[A]の保持するオブジェクトを受け取って、新しいResultモナドを返す関数fを引数に取ります。関数fの戻り値Resultの型パラメータは、基本的にどんな型でも返せるようにします。ただし、flatMapは、Bad[B]を返す可能性があるので、Badの型は、型Bに関連する型にしなければなりません。型Bと関数fが返すBadの型を共通にするために、戻り値のBadの型は、型Bの親型のBB >: Bにします。

    def flatMap[C, BB >: B]( f: A => Result[C, BB] ): Result[C, BB] = this match {
      case Good( a ) => f( a )
      case Bad( b ) => Bad( b )
      case _ => Empty
    }

ソースコード

Resultモナドのソースコード次のようになりました。
Haskellのモナドを繋げる>>=>>もついでに追加してみました。

10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
  abstract class Result[+A, +B] {

    def getGood: A = this match {
      case Good( a ) => a
      case _ => throw new java.util.NoSuchElementException("this instance is not Good")
    }

    def getBad: B = this match {
      case Bad( b ) => b
      case _ => throw new java.util.NoSuchElementException("this instance is not Bad")
    }

    def get: A = getGood

    def getOrElse[AA >: A]( a: => AA ): AA = this match {
      case Good( a ) => a
      case _ => a
    }

    def orElse[AA >: A, BB >: B]( r: => Result[AA, BB] ): Result[AA, BB] = this match {
      case Good( a ) => Good( a )
      case _ => r
    }

    def exists( f: A => Boolean ): Boolean = this match {
      case Good( a ) => f( a )
      case _ => false
    }

    def forall( f: A => Boolean ): Boolean = this match {
      case Good( a ) => f( a )
      case _ => false
    }

    def foreach( f: A => Unit ): Unit = this match {
      case Good( a ) => f( a )
      case _ => ()
    }

    def filter( f: A => Boolean ): Result[A, B] = this match {
      case Good( a ) => if( f( a ) ) Good( a ) else Empty
      case Bad( b ) => Bad( b )
      case _ => Empty
    }

    def flatMap[C, BB >: B]( f: A => Result[C, BB] ): Result[C, BB] = this match {
      case Good( a ) => f( a )
      case Bad( b ) => Bad( b )
      case _ => Empty
    }

    def map[C]( f: A => C ): Result[C, B] = this match {
      case Good( a ) => Good( f(a) )
      case Bad( b ) => Bad( b )
      case _ => Empty
    }

    def >>= [C, BB >: B] ( f: A => Result[C, BB] ): Result[C, BB] = flatMap( f )

    def >> [C, BB >: B] ( f: => Result[C, BB] ): Result[C, BB] = flatMap( _ => f )

    def >>-[C]( f: A => C ): Result[C, B] = map( f )

    def toOption: Option[A] = this match {
      case Good( a ) => Some( a )
      case _ => None
    }

    def toList: List[A] = this match {
      case Good( a ) => List( a )
      case _ => Nil
    }

    def toBoolean: Boolean = this match {
      case Good( _ ) => true
      case _ => false
    }

  }


  case class Good[+A, +B]( a: A ) extends Result[A, B]

  case class Bad[+A, +B]( b: B ) extends Result[A, B]

  case object Empty extends Result[Nothing, Nothing]


0 件のコメント:

コメントを投稿