ScalaのEitherモナドが正直言って使い勝ってが悪いので、自分なりに使い勝手がよいモナドを作ってみました。
例えば、次のようにEitherを4つ繋げるときです。
この結果は、Left("error")を返します。
ここで面倒くさいのが、Rightで繋げていきたいときは、.right、Leftで繋げていきたいときは、.leftメソッド呼び出さないといけないことです。僕がEitherモナドを使うときは、Rightに成功したときの値でLeftに失敗した時のメッセージなどを入れることがほとんどなので、.leftを使うことは、ほぼ有りません。.rightを省いて使いたいのです。
僕にとっては、次のように使えるモナドがあれば都合がいいです。
こんなふうに、Goodのときは、どんどん繋がっていき、途中でBadがあったら、そこで止まるというようなモナドがあったら便利です。
そこで自分で都合の良いモナドを作ってみることにしました。
forで使うには、foreach、filter、map、flatMapを定義する必要があります。
他のモナドにもあるような、get、getOrElse、orElse、exists、forallなどのメソッドも実装します。
Haskellのモナドを繋げる>>=と>>もついでに追加してみました。
Eitherモナド
Eitherモナドのどういうところが使い勝手が悪いかと言うと、forとかでEitherモナドをつなげて使う時です。例えば、次のようにEitherを4つ繋げるときです。
1
2
3
4
5
6
| 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で繋げていきたいときは、.right、Leftで繋げていきたいときは、.leftメソッド呼び出さないといけないことです。僕がEitherモナドを使うときは、Rightに成功したときの値でLeftに失敗した時のメッセージなどを入れることがほとんどなので、.leftを使うことは、ほぼ有りません。.rightを省いて使いたいのです。
僕にとっては、次のように使えるモナドがあれば都合がいいです。
1
2
3
4
5
6
| 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で使うには、foreach、filter、map、flatMapを定義する必要があります。
他のモナドにもあるような、get、getOrElse、orElse、exists、forallなどのメソッドも実装します。
型パラメータ
Resultは、2つの型パラーメータを取ります。それぞれ、GoodとBadの保持する型です。Emptyが保持する型は、全ての型のサブクラスとして返す必要があるので、Nothingにします。このため、Resultの2つの型パラメータを共変にしなければなりません。1
2
3
4
| 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にします。1
2
3
4
5
| 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のモナドを繋げる>>=と>>もついでに追加してみました。
1
2
3
4
5
6
7
8
9
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 件のコメント:
コメントを投稿