2012年3月1日木曜日

Scala Tips / Either (14) - 二項演算, AND, Plus

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

今回は「Either:AND」×「値:Plus」を考えます。

前々回は「Either:AND」×「値:任意の関数で計算」、前回は「Either:AND」×「値:Monoidで計算」でしたが、値の計算方法を「任意の関数で計算」や「Monoidで計算」から「Plusで計算」に変えたものになります。Plusはちょっと謎な性質なのですが、Scalazで用意されているので、動作確認という意味もあり試してみました。

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

EitherのAND
lhsrhs結果Rightの値Leftの値
RightRightRight二項演算-
RightLeftLeft-rhs
LeftRightLeft-lhs
LeftLeftLeft-二項演算

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

lhs/rhsともRight
Plus
lhs/rhsともLeft
lhs側を使う

Scala標準ライブラリではPlusは提供されていないので、この組み合わせが可能なのはScalazの場合です。Java風、Scala風、ScalaはMonoidの場合と同じになるので省略して、ScalazでPlusを使った実装を行います。

Scalaz

引数の型を型クラスPlusに対応する型パラメータMとします。また型パラメータMは高カインド型で、型パラメータAを取ります。この場合、型パラメータMは、コンテキスト・バウンドを使って「M[_]: Plus」と指定します。

ScalazではPlus同士の加算演算として、演算子 <+> を用意しているので、これを使います。

Scalazでは、RightProjectionだけではなくEitherも成功/失敗文脈のモナドとして使えるのと、flatMapメソッドとして>>=メソッドを使うことができるので、以下のようになります。

def f[M[_]: Plus, A](e1: Either[Throwable, M[A]], e2: Either[Throwable, M[A]]): Either[Throwable, M[A]] = {
  e1 >>= (e1r => e2.map(e2r => e1r <+> e2r))
}

試してみたところ、型クラスPlusはコンテナ同士のOR演算を行うようです。

Scalazでは、ListやOptionなどコンテナ的に使用できるオブジェクトの多くが型クラスPlus型として定義されているので、Plusに対するロジックをそのまま適用することができます。以下は実際に動いている様子です。

scala> f(List(1, 2).right, List(3, 4).right)
res100: Either[Throwable,List[Int]] = Right(List(1, 2, 3, 4))
scala> f(Map(1 -> 10, 2 -> 20).right, Map(3 -> 30, 4 -> 40).right)
res103: Either[Throwable,scala.collection.immutable.Iterable[(Int, Int)]] = Right(List((1,10), (2,20), (3,30), (4,40)))
scala> f(1.some.right, 2.some.right)
res104: Either[Throwable,Option[Int]] = Right(Some(1))
scala> f(none.right, 2.some.right)
res106: Either[Throwable,Option[Int]] = Right(Some(2))

ListとMapは、コンテナが自然に結合されています。

Optionについては、コンテナ内に格納できる要素が一つしかありません。そこで、短絡評価ORの計算が行われるようです。モノイドの場合は、格納される値に対する二項演算が行われるので、その点が違いとなります。

for

for式でもrightメソッドでRightProjectionを取り出す処理は省略できます。

Plus同士の加算演算として、オペレータ <+> を使います。

def f[M[_]: Plus, A](e1: Either[Throwable, M[A]], e2: Either[Throwable, M[A]]): Either[Throwable, M[A]] = {
  for {
    e1r <- e1
    e2r <- e2
  } yield e1r <+> e2r
}
Applicative Functor

Scalazでは、Applicative Functorを使って、2つ(またはそれ以上)のEitherに対して二項演算(N項演算)することができます。

Plus同士の加算演算として、演算子 <+> を使います。演算子 <+> を使う場合には、「(e1|@|e2)(f)」といった形でメソッド名のみを指定する省略形は使えないので、引数を指定する必要があります。

def f[M[_]: Plus, A](e1: Either[Throwable, M[A]], e2: Either[Throwable, M[A]]): Either[Throwable, M[A]] = {
  (e1 |@| e2)(_ <+> _)
}

e1とe2が共にRightの場合、関数fの第1引数にe1(Right)の値、第2引数e2(Right)の値を適用して評価し、その結果をRightに詰めて返すという動作をします。

ノート

型クラスPlusを用いて、コンテナ・オブジェクトに対して演算子 <+> による共通のロジックを適用することができました。

型クラスMonoidは、オブジェクトに対する二項演算なので、演算子 |+| はscalaz.Identityに定義されていますが、型クラスPlusは、コンテナ・オブジェクトに対する二項演算なので、演算子 <+> はscalaz.MAに定義されています。

型クラスMonoidと型クラスPlusの使い分けは、このあたりのメカニズムも意識しておくとよいでしょう。

型クラスとコンテキスト・バウンド

PlusバージョンのプログラムでもMonoidバージョンと同様に、Int型ではなくて、コンテキスト・バウンドの記述方法[T: Plus]を用いてPlus型のオブジェクトを処理対象として宣言しました。

コンテキスト・バウンドを使わず、暗黙パラメタを使って、本文の処理を定義すると以下のようになります。

def f[M[_], A](e1: Either[Throwable, M[A]], e2: Either[Throwable, M[A]])(implicit p: Plus[M]): Either[Throwable, M[A]] = {
  (e1 |@| e2)(_ <+> _)
}

Monoidでは、型パラメータTに対するコンテキスト・バウンドでしたが、Plusでは、高カインド型の型パラメータMに対するコンテキスト・バウンドを使用しました。高カインド型の型パラメータでは、引数となる型パラメータは並置して宣言して、メソッド引数などで組合せを指定します。高カインド型の型パラメータを扱うときは、ちょっとコツが必要なので注意が必要です。

諸元

  • Scala 2.9.1
  • Scalaz 6.0.3

0 件のコメント:

コメントを投稿