EitherとValidationの相互変換のイディオムです。
scalaz.ValidationはScalazが提供する「検証の文脈」のモナドです。「成功と失敗の文脈」としても使うことができるので、OptionやEitherと適材適所で使い分けしていくことになります。
Either編の後はValidation編に入る予定ですが、前回「Optionと相互変換」でも「o.toSuccess(e).either」という形で登場したので、少し内容を先取りして取り上げます。
(分類の基準)
Java風
Validation→Either
ValidationをEitherに変換する場合は、if式による判定を用いて、Validationの成否に対応する値をLeftまたはRightに詰めます。
Validationでは、成功または失敗の値を直接取り出す方法がないので、キャストを用いています。あまり美しくないですね。
def f[T](v: Validation[Throwable, T]): Either[Throwable, T] = { if (v.isSuccess) Right(v.asInstanceOf[Success[Throwable, T]].a) else Left(v.asInstanceOf[Failure[Throwable, T]].e) }
キャストはちょっと筋が悪いので、美しさを追求する場合は、一度Optionに変換する方法もあります。
def f[T](v: Validation[Throwable, T]): Either[Throwable, T] = { if (v.isSuccess) Right(v.toOption.get) else Left(v.fail.toOption.get) }
Either→Validation
EitherをValidationに変換する場合は、EitherのisRihtメソッドを用いて判定し、rightメソッドで取り出したRightProjectionまたはleftメソッドで取り出したLeftProjectionからgetメソッドで値を取り出し、SuccessまたはFailureに詰めます。
def f[T](e: Either[Throwable, T]): Validation[Throwable, T] = { if (e.isRight) Success(e.right.get) else Failure(e.left.get) }
Scala風
Validation→Either
ValidationをEitherに変換する場合は、match式でSuccessまたはFailureへの判定を用いて、Validationの成否に対応する値をLeftまたはRightに詰めます。
def f[T](v: Validation[Throwable, T]): Either[Throwable, T] = { v match { case Success(s) => Right(s) case Failure(f) => Left(f) } }
Either→Validation
EitherをValidationに変換する場合は、match式でLeftまたはRightへの判定を用いて、Eitherの成否に対応する値をSuccessまたはFailureに詰めます。
def f[T](e: Either[Throwable, T]): Validation[Throwable, T] = { e match { case Right(r) => Success(r) case Left(l) => Failure(l) } }
Scala
ValidationはScalazが提供するモナドですが、ここではScala的なコーディングということでfoldメソッドを用いた方法を紹介します。
Validation→Either
Validationのfoldメソッドの第1引数に失敗側の値をLeftに変換する関数を、第2引数に成功側の値をRightに変換する関数を指定します。
def f[T](v: Validation[Throwable, T]): Either[Throwable, T] = { v.fold(Left(_), Right(_)) }
Scalazの機能を用いて、よりコンパクトに書くと以下のようになります。
def f[T](v: Validation[Throwable, T]): Either[Throwable, T] = { v.fold(_.left, _.right) }
Either→Validation
Eitherのfoldメソッドの第1引数に左側の値をFailureに変換する関数を、第2引数に右側の値をSuccessに変換する関数を指定します。
def f[T](e: Either[Throwable, T]): Validation[Throwable, T] = { e.fold(Failure(_), Success(_)) }
Scalazの機能を用いて、よりコンパクトに書くと以下のようになります。
def f[T](e: Either[Throwable, T]): Validation[Throwable, T] = { e.fold(_.fail, _.success) }
Scalaz
Validation→Either
Validationには、Eitherに変換するためのeitherメソッドが用意されているので、これを用います。
def f[T](v: Validation[Throwable, T]): Either[Throwable, T] = { v.either }
Either→Validation
Scalazでは、validation関数でEitherをValidationに変換することができます。
def f[T](e: Either[Throwable, T]): Validation[Throwable, T] = { validation(e) }
追記:2012-04-16
Either→ValidationのScalaz版の内容が間違っていたので書き換えました。
ノート
ValidationとEitherの相互変換は、ダイレクトに変換するメソッドが提供されているので、これを用いればよいのですが、Scalaでのプログラミングの考え方をおさらいするのによい例題なのでJava風、Scala風、Scalaの項目も考えてみました。
何かの処理をScalaでプログラミングする場合、以下の順番でロジックを探す事になると思います。
- Scalaz/Scalaでドンピシャのメソッドを見つける
- fold系やmap, flatMapといった高階関数を使ってロジックを考える
- match式を使って場合分けをする
- if式を使って場合分けをする
この順番は、Scala Tipsで用いている分類の基準であるScalaz、Scala、Scala風、Java風とも近しいものになっています。
個人的には(パターンマッチングや再帰呼び出し、代数的データ型の選択といったところではなく)成否判定にmatch式が出てきたら負けっぽい感じで考えています。if式はさらに負け度が大きい感じです。この場合、できるだけ2の「fold系やmap, flatMap」に持ち込むように考えていきます。
なぜ、「fold系やmap, flatMap」の方が好ましいのかというと、感覚的には、手続きではなく宣言に近づいていくから、ということになります。具体的なメリットとしては、(1)ロジックを簡潔に記述することができる、(2)部品(関数)の再利用の可能性が高まる、ということです。
OOPでは「fold系やmap, flatMap」という切り口のプログラミングはあまりないので、OOPプログラマがScala(関数型言語)を使う上で意識しておくべきコツと言うことができそうです。
諸元
- Scala 2.9.1
- Scalaz 6.0.3
0 件のコメント:
コメントを投稿