2012年3月2日金曜日

Scala Tips / Either (15) - 二項演算, OR

Rightを成功とする、成功/失敗文脈におけるEitherに対する二項演算です。(Scala Tips / Either (9) - 二項演算)

前回までは、「Either:AND」について「Either:AND」×「値:任意の関数で計算」「Either:AND」×「値:Monoid」「Either:AND」×「値:Plus」についてみてきました。

今回は、「Either:OR」×「値:任意の関数で計算」を考えます。

EitherのORは以下の演算になります。

EitherのOR
lhsrhs結果Rightの値Leftの値
RightRightRight二項演算-
RightLeftRightlhs-
LeftRightRightrhs-
LeftLeftLeft-二項演算

lhsとrhsの両方がLeft(失敗)でない場合は、Right(成功)となります。

値に対する二項演算は、lhs/rhsともRightだった場合と、Leftだった場合があります。

値に対する二項演算は、以下のものが考えられます。

lhs
lhs側を使う
rhs
rhs側を使う
  • f(lhs, rhs) :: 任意の関数で計算
  • lhs |+| rhs :: Monoidで計算
  • lhs <+> rhs :: Plusで計算

値に対する二項演算は以下の組合せとします。

lhs/rhsともRight
任意の関数で計算
lhs/rhsともLeft
lhs側を使う

(分類の基準)

Java風

if式を使って、4つの場合を記述します。

def f(e1: Either[Throwable, Int], e2: Either[Throwable, Int], f: (Int, Int) => Int): Either[Throwable, Int] = {
  if (e1.isRight && e2.isRight) {
    Right(f(e1.right.get, e2.right.get)) // Rightの二項計算
  } else if (e1.isRight && e2.isLeft) {
    e1
  } else if (e1.isLeft && e2.isRight) {
    e2
  } else { // e1.is Left && e2.isLeft
    e1 // Leftの二項演算
  }
}

Scala風

match式を使って、4つの場合を記述します。

def f(e1: Either[Throwable, Int], e2: Either[Throwable, Int], f: (Int, Int) => Int): Either[Throwable, Int] = {
  e1 match {
    case Right(e1r) => e2 match {
      case Right(e2r) => Right(f(e1r, e2r)) // Rightの二項計算
      case Left(_) => e1
    }
    case Left(_) => e2 match {
      case Right(_) => e2
      case Left(_) => e1 // Leftの二項演算
    }
  }
}

match式のネストが気に入らない場合は以下のようにすればネストしない方式で記述することもできます。

def f(e1: Either[Throwable, Int], e2: Either[Throwable, Int], f: (Int, Int) => Int): Either[Throwable, Int] = {
  (e1, e2) match {
    case (Right(e1r), Right(e2r)) => Right(f(e1r, e2r)) // Rightの二項計算
    case (Right(_), Left(_)) => e1
    case (Left(_), Right(_)) => e2
    case (Left(_), Left(_)) => e1 // Leftの二項計算
  }
}

後者(Tuple方式)は、Tupleを導入しているのとパターンマッチングの回数が増えるので性能的には不利ですが、プログラムの見通しはよくなります。フレームワークで使う場合には性能重視で前者(ネスト方式)、アプリケーションで使う場合には可読性重視で後者(Tuple方式)という選択も考えられます。

Scala

Eitherに対するORを行うScalaらしい関数合成、Monadic演算による方式を見つけることができませんでした。Scala風で説明した方法を用いることになります。

Scalaz

Scalazでも、Eitherに対するORを行うScalaらしい関数合成、Monadic演算による方式を見つけることができませんでした。Scala風で説明した方法を用いることになります。

ノート

Scala Tips / Either (10) - 二項演算, ANDScala Tips / Either (13) - 二項演算, AND, MonoidScala Tips / Either (14) - 二項演算, AND, Plusの3つはflatMapを使った関数合成やApplicative Functorの利用がぴったりとはまりました。

一方、今回はEitherに対するORを行おうとしたわけですが、ぴったりはまるよい方法を見つけることができませんでした。

scalaz.Traverseあたりを使うと何とかなりそうな気もしていたのですが、TraverseもflatMapと同様にANDのセマンティクスのようです。Optionに変換する方法はLeftの情報がなくなってしまうのでダメです。

Eitherに対するOR演算は、今後の研究課題にしたいと思います。

諸元

  • Scala 2.9.1
  • Scalaz 6.0.3

3 件のコメント:

  1. このコメントは投稿者によって削除されました。

    返信削除
  2. https://twitter.com/#!/halcat0x15a/status/175973874968694784

    https://twitter.com/#!/halcat0x15a/status/175974439589122048

    返信削除
  3. この方法ではうまくいかないようです。

    返信削除