Rightを成功とする、成功/失敗文脈におけるEitherに対する二項演算です。
今回は「Either:AND」×「値:Plus」を考えます。
前々回は「Either:AND」×「値:任意の関数で計算」、前回は「Either:AND」×「値:Monoidで計算」でしたが、値の計算方法を「任意の関数で計算」や「Monoidで計算」から「Plusで計算」に変えたものになります。Plusはちょっと謎な性質なのですが、Scalazで用意されているので、動作確認という意味もあり試してみました。
EitherのANDは以下の演算になります。
lhs | rhs | 結果 | Rightの値 | Leftの値 |
---|---|---|---|---|
Right | Right | Right | 二項演算 | - |
Right | Left | Left | - | rhs |
Left | Right | Left | - | lhs |
Left | Left | Left | - | 二項演算 |
値に対する二項演算は以下の組合せとします。
- 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 件のコメント:
コメントを投稿