Eitherを成功/失敗の文脈で使用する方法のイディオムです。
前回はEitherをOptionと同じ成功/失敗の文脈で使用する方法について見てきました。
これは以下の演算になりますが、ちょうど、Optionでいうと「 Option (3) - map 」に相当する処理です。
条件 | 結果 |
---|---|
Either[A, B]がRight[A, B] | Right[A, C] |
Either[A, B]がLeft[A, B] | Left[A, C] |
今回は以下の処理、アプリケーションの意図で成功の文脈を失敗の文脈に切り替えるためのMonadic演算についてみていきます。Optionでいうと「Option (6) - flatMap 」に相当する処理になります。
条件 | 結果 |
---|---|
Either[A, B]のRight[A, B]に有効な値が入っている | Right[A, C] |
Either[A, B]のRight[A, B]に無効な値が入っている | Left[A, C] |
Either[A, B]がLeft[A, B] | Left[A, C] |
大枠ではB→Cの演算を行いたいわけですが、これをEither[A, B→C]の文脈の上で行います。この時、Either[A, B]がRight[A, B]であっても「B」が無効な値である場合には、Left[A, C]にすることで、成功の文脈から失敗の文脈へ切り替えます。
以下では、Either[Exception, Int]からEither[Exception, String]への変換を例に考えてみます。ただし、Intは0以上のものが有効という条件を追加します。Either(Right)に入っているIntが0以上の場合、Right[Exception, String]が処理結果となります。一方、0未満の場合は無効となりLeft[Exception, String]が処理結果となります。
Java風
if式でEither#isRightメソッドを使ってLeftとRightの判定をして、処理を切り分ける事ができます。
def f(a: Either[Exception, Int]): Either[Exception, String] = { if (a.isRight) { if (a.right.get >= 0) { Right(a.right.get.toString) } else { Left(new IllegalArgumentException("less than 0")) } } else { a.asInstanceOf[Either[Exception, String]] } }
Scala風
match式を使うとLeftとRightのパターンマッチングで綺麗に書くことができます。ただし、Scala的にはLeft(b)の場合はLeft(b)というロジックを書くのが悔しい。
def f(a: Either[Exception, Int]): Either[Exception, String] = { a match { case Right(b) if (b >= 0) => Right(b.toString) case Right(_) => Left(new IllegalArgumentException("less than 0")) case Left(b) => Left(b) } }
Scala
Eitherのrightメソッドで得られるRightProjectionがモナドっぽい動きをするので、flatMapメソッドを使ってMonadicプログラミングします。flatMapメソッドでは、Int値が0以上である場合は有効な値なのでStringに変換する演算を行い結果をRightオブジェクトに詰めて返します。一方、Int値が0未満の場合は、無効な値なので成功の文脈から失敗の文脈への切り替えとして、LeftオブジェクトにIllegalArgumentExceptionを詰めて返します。
def f(a: Either[Exception, Int]): Either[Exception, String] = { a.right.flatMap { b => if (b >= 0) Right(b.toString) else Left(new IllegalArgumentException("less than 0")) } }
Scalaz
Scalazを使うと、Eitherが右側を成功の文脈として動作するモナドに拡張されるので、これを利用したプログラミングが可能です。やはりflatMapメソッドを使います。また、LeftとRightの生成をleftメソッド、rightメソッドで簡潔に記述できるようになります。
def f(a: Either[Exception, Int]): Either[Exception, String] = { a.flatMap { b => if (b >= 0) b.toString.right else new IllegalArgumentException("less than 0").left } }
ノート
前回は、Eitherのrightメソッドで返ってくるRightProjectionを使えばMonadic演算が可能なことを説明しました。
RightProjectionを使って、Optionで使用したwithFilter, collect, flatMap, for式といった技が駆使できると嬉しいのですが、残念ながらそうはなっていません。以下の理由により、この中で使える/使って便利なのはflatMapのみとなります。
collectメソッドはRightProjectionにそもそも機能がありません。
withFilterメソッドやfilterメソッドはEither[A, B]ではなくOption[Either[A, B]]を返すので、Optionのハンドリングが余分に必要になります。これは、それなりのコードになってしまうので、それよりflatMapメソッドを使ったほうが簡潔です。
for式は内部的にwithFilterメソッドを使っているので、やはり使えません。
以上の理由で、Eitherに対しては他の機能のことは考えずに、文脈の切り替えはflatMapメソッド一本で考えていくのが得策です。
Scalazの場合も事情は同じで(Left/RightProjectionではなく)EitherのflatMapメソッドを使って処理を記述することになります。
追記 (2012-02-14)
ひなたねこさんのツイートでよりScalazらしい書き方が判明したので補足します。
以下はEither[Exception, Int]の生成にBooleanのeitherメソッドを使ったバージョンです。Right(…)、….rightやLeft(…)、….leftが出てこないので、より簡潔でScalazらしいですね。
def f(a: Either[Exception, Int]): Either[Exception, String] = { a.flatMap { b => !(b >= 0) either { new IllegalArgumentException("less than 0") } or b.toString } }またScala版の記述をleftメソッド、rightメソッドを使うものに更新しました。
諸元
- Scala 2.9.1
- Scalaz 6.0.3
0 件のコメント:
コメントを投稿