2012年2月13日月曜日

Scala Tips / Either

Eitherは直和(disjoint union)を表現するオブジェクトです。数学的な意味はこちらを参照してください。

直和は、プログラミング的な観点でざっくりというと二つの種類の値のどちらかを選択する(choice)という意味です。

Eitherでは、2つの種類の値のことをそれぞれleftとrightと呼んでいます。以下ではleftの値を「左側」、rightの値を「右側」と表現することにします。

Eitherの典型的な使い方は、関数の成功と失敗を、成功時の返却値、失敗時の返却値と合わせて通知するというものです。EitherのサブクラスはRightとLeftの2つで、成功の場合はRight、失敗の場合はLeftを使う慣習になっています。右側のRightを成功の用途に使うという慣習は、いうまでもなく「Right=正しい」という英語の意味にかけています。

Eitherを使ってOptionと同様の成功/失敗の計算文脈上でのMonadicプログラミングをすることが可能です。

今回はOption(3)の課題のEither版を考えてみます。

条件結果
Either[A, B]がRight[A, B]Right[A, C]
Either[A, B]がLeft[A, B]Left[A, C]

大枠ではB→Cの演算を行いたいわけですが、これをEither[A, B→C]の文脈の上で行うわけです。

以下で左側(失敗側)にException、右側(成功側)にIntを持つをEither、つまりEither[Exception, Int]をEither[Exception, String]に変換するプログラムを考えます。

(分類の基準)

Java風

if式でEither#isRightメソッドを使ってLeftとRightの判定をして、処理を切り分ける事ができます。

def f(a: Either[Exception, Int]): Either[Exception, String] = {
  if (a.isRight) {
    Right(a.right.get.toString)
  } 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) => Right(b.toString)
    case Left(b) => Left(b)
  }
}

Scala

Eitherのrightメソッドで得られるRightProjectionがモナドっぽい動きをするので、これを使ってMonadicプログラミングします。

def f(a: Either[Exception, Int]): Either[Exception, String] = {
  a.right.map(_.toString)
}

RightProjectionそのものはモナドではなく、EitherとRightProjectionを合わせて一つのモナド、というような動きになります。たとえば、mapメソッドを繋げる場合は以下のようになります。

a.right.map(_.toString).right.map(_.toInt)

Scalaz

Scalazを使うと、Eitherが右画はを成功の文脈として動作するモナドに拡張されるので、これを利用したプログラミングが可能です。

def f(a: Either[Exception, Int]): Either[Exception, String] = {
  a.map(_.toString)
}

ScalazではEitherが、エラー情報付きのOption的な機能を持つオブジェクト/モナドになるわけです。

ノート

ScalaのEitherはモナドではないので、Monadicな演算、つまり計算文脈上で値を操作する演算をするためにはちょっとコツが要ります。

Eitherのleftメソッド、rightメソッドで得られるLeftProjection、RightProjectionがモナド的な動きをします。EitherとLeftProjection、RightProjectionを合わせて一つのモナドという使い方になります。

Eitherでは左側と右側を公平に扱っています。左側を主にMonadic演算をする場合は右側は自動的に引き継がれる、逆に右側を主にMonadic演算をする場合は左側は自動的に生き継がれる、という動きになります。左側が主の場合は、leftメソッドで得られるLeftProjection、右側が主の場合は、rightメソッドで得られるRightProjectionを使うわけです。直和を表現するという趣旨からは妥当な仕様です。

しかし、Eitherの典型的な使い方である成功/失敗の文脈の用途では、右側を成功の文脈とするのが慣習となっているため、Either自身が右側を成功の文脈とするモナドである方が使い勝手が良くなります。

Scalazを使うと、まさにこのEitherの右側を成功の文脈とするモナドの機能が付加されます。

日々のプログラミングでは、Option的な成功/失敗の文脈が、Eitherの主たる用途になるのでScalazの拡張を利用するとよいでしょう。

Scala基本クラスの提供する左側と右側が平等に扱われるMonadic演算も、応用にハマれば面白い使い方ができそうです。

追記 (2012-02-13)

よい表現を思いついたので更新しました。
  • 二つの種類の値のどちらかを取る→二つの種類の値どちらかを選択する(choice)

諸元

  • Scala 2.9.1
  • Scalaz 6.0.3

0 件のコメント:

コメントを投稿