2012年4月11日水曜日

Scala Tips / Either (20) - Validationと相互変換

EitherとValidationの相互変換のイディオムです。

scalaz.ValidationはScalazが提供する「検証の文脈」のモナドです。「成功と失敗の文脈」としても使うことができるので、OptionやEitherと適材適所で使い分けしていくことになります。

Either編の後はValidation編に入る予定ですが、前回「Optionと相互変換」でも「o.toSuccess(e).either」という形で登場したので、少し内容を先取りして取り上げます。

(分類の基準)

Java風

Validation→Either

ValidationをEitherに変換する場合は、if式による判定を用いて、Validationの成否に対応する値をLeftまたはRightに詰めます。

Validationでは、成功または失敗の値を直接取り出す方法がないので、キャストを用いています。あまり美しくないですね。

  1. def f[T](v: Validation[Throwable, T]): Either[Throwable, T] = {  
  2.   if (v.isSuccess) Right(v.asInstanceOf[Success[Throwable, T]].a)  
  3.   else Left(v.asInstanceOf[Failure[Throwable, T]].e)  
  4. }  

キャストはちょっと筋が悪いので、美しさを追求する場合は、一度Optionに変換する方法もあります。

  1. def f[T](v: Validation[Throwable, T]): Either[Throwable, T] = {  
  2.   if (v.isSuccess) Right(v.toOption.get)  
  3.   else Left(v.fail.toOption.get)  
  4. }  
Either→Validation

EitherをValidationに変換する場合は、EitherのisRihtメソッドを用いて判定し、rightメソッドで取り出したRightProjectionまたはleftメソッドで取り出したLeftProjectionからgetメソッドで値を取り出し、SuccessまたはFailureに詰めます。

  1. def f[T](e: Either[Throwable, T]): Validation[Throwable, T] = {  
  2.   if (e.isRight) Success(e.right.get)  
  3.   else Failure(e.left.get)  
  4. }  

Scala風

Validation→Either

ValidationをEitherに変換する場合は、match式でSuccessまたはFailureへの判定を用いて、Validationの成否に対応する値をLeftまたはRightに詰めます。

  1. def f[T](v: Validation[Throwable, T]): Either[Throwable, T] = {  
  2.   v match {  
  3.     case Success(s) => Right(s)  
  4.     case Failure(f) => Left(f)  
  5.   }  
  6. }  
Either→Validation

EitherをValidationに変換する場合は、match式でLeftまたはRightへの判定を用いて、Eitherの成否に対応する値をSuccessまたはFailureに詰めます。

  1. def f[T](e: Either[Throwable, T]): Validation[Throwable, T] = {  
  2.   e match {  
  3.     case Right(r) => Success(r)  
  4.     case Left(l) => Failure(l)  
  5.   }  
  6. }  

Scala

ValidationはScalazが提供するモナドですが、ここではScala的なコーディングということでfoldメソッドを用いた方法を紹介します。

Validation→Either

Validationのfoldメソッドの第1引数に失敗側の値をLeftに変換する関数を、第2引数に成功側の値をRightに変換する関数を指定します。

  1. def f[T](v: Validation[Throwable, T]): Either[Throwable, T] = {  
  2.   v.fold(Left(_), Right(_))  
  3. }  

Scalazの機能を用いて、よりコンパクトに書くと以下のようになります。

  1. def f[T](v: Validation[Throwable, T]): Either[Throwable, T] = {  
  2.   v.fold(_.left, _.right)  
  3. }  
Either→Validation

Eitherのfoldメソッドの第1引数に左側の値をFailureに変換する関数を、第2引数に右側の値をSuccessに変換する関数を指定します。

  1. def f[T](e: Either[Throwable, T]): Validation[Throwable, T] = {  
  2.   e.fold(Failure(_), Success(_))  
  3. }  

Scalazの機能を用いて、よりコンパクトに書くと以下のようになります。

  1. def f[T](e: Either[Throwable, T]): Validation[Throwable, T] = {  
  2.   e.fold(_.fail, _.success)  
  3. }  

Scalaz

Validation→Either

Validationには、Eitherに変換するためのeitherメソッドが用意されているので、これを用います。

  1. def f[T](v: Validation[Throwable, T]): Either[Throwable, T] = {  
  2.   v.either  
  3. }  
Either→Validation

Scalazでは、validation関数でEitherをValidationに変換することができます。

  1. def f[T](e: Either[Throwable, T]): Validation[Throwable, T] = {  
  2.   validation(e)  
  3. }  
追記:2012-04-16

Either→ValidationのScalaz版の内容が間違っていたので書き換えました。

ノート

ValidationとEitherの相互変換は、ダイレクトに変換するメソッドが提供されているので、これを用いればよいのですが、Scalaでのプログラミングの考え方をおさらいするのによい例題なのでJava風、Scala風、Scalaの項目も考えてみました。

何かの処理をScalaでプログラミングする場合、以下の順番でロジックを探す事になると思います。

  1. Scalaz/Scalaでドンピシャのメソッドを見つける
  2. fold系やmap, flatMapといった高階関数を使ってロジックを考える
  3. match式を使って場合分けをする
  4. 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 件のコメント:

コメントを投稿