Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't combine lefts on Xor and XorT combine #1034

Merged
merged 2 commits into from
May 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions core/src/main/scala/cats/data/Xor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,43 @@ sealed abstract class Xor[+A, +B] extends Product with Serializable {

def merge[AA >: A](implicit ev: B <:< AA): AA = fold(identity, ev.apply)

final def combine[AA >: A, BB >: B](that: AA Xor BB)(implicit AA: Semigroup[AA], BB: Semigroup[BB]): AA Xor BB = this match {
case Xor.Left(a1) => that match {
case Xor.Left(a2) => Xor.Left(AA.combine(a1, a2))
case Xor.Right(b2) => Xor.Left(a1)
}
/**
* Combine with another `Xor` value.
*
* If this `Xor` is a `Left` then it will be returned as-is.
* If this `Xor` is a `Right` and `that` `Xor` is a left, then `that` will be
* returned.
* If both `Xor`s are `Right`s, then the `Semigroup[BB]` instance will be used
* to combine both values and return them as a `Right`.
* Note: If both `Xor`s are `Left`s then their values are not combined. Use
* `Validated` if you prefer to combine `Left` values.
*
* Examples:
* {{{
* scala> import cats.data.Xor
* scala> import cats.implicits._
* scala> val l1: Xor[String, Int] = Xor.left("error 1")
* scala> val l2: Xor[String, Int] = Xor.left("error 2")
* scala> val r3: Xor[String, Int] = Xor.right(3)
* scala> val r4: Xor[String, Int] = Xor.right(4)
*
* scala> l1 combine l2
* res0: Xor[String, Int] = Left(error 1)
*
* scala> l1 combine r3
* res1: Xor[String, Int] = Left(error 1)
*
* scala> r3 combine l1
* res2: Xor[String, Int] = Left(error 1)
*
* scala> r3 combine r4
* res3: Xor[String, Int] = Right(7)
* }}}
*/
final def combine[AA >: A, BB >: B](that: AA Xor BB)(implicit BB: Semigroup[BB]): AA Xor BB = this match {
case left @ Xor.Left(_) => left
case Xor.Right(b1) => that match {
case Xor.Left(a2) => Xor.Left(a2)
case left @ Xor.Left(_) => left
case Xor.Right(b2) => Xor.Right(BB.combine(b1, b2))
}
}
Expand Down Expand Up @@ -168,7 +198,7 @@ private[data] sealed abstract class XorInstances extends XorInstances1 {
def show(f: A Xor B): String = f.show
}

implicit def xorMonoid[A, B](implicit A: Semigroup[A], B: Monoid[B]): Monoid[A Xor B] =
implicit def xorMonoid[A, B](implicit B: Monoid[B]): Monoid[A Xor B] =
new Monoid[A Xor B] {
def empty: A Xor B = Xor.Right(B.empty)
def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y
Expand Down Expand Up @@ -229,7 +259,7 @@ private[data] sealed abstract class XorInstances extends XorInstances1 {

private[data] sealed abstract class XorInstances1 extends XorInstances2 {

implicit def xorSemigroup[A, B](implicit A: Semigroup[A], B: Semigroup[B]): Semigroup[A Xor B] =
implicit def xorSemigroup[A, B](implicit B: Semigroup[B]): Semigroup[A Xor B] =
new Semigroup[A Xor B] {
def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y
}
Expand Down
40 changes: 39 additions & 1 deletion core/src/main/scala/cats/data/XorT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,45 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) {

def merge[AA >: A](implicit ev: B <:< AA, F: Functor[F]): F[AA] = F.map(value)(_.fold(identity, ev.apply))

def combine(that: XorT[F, A, B])(implicit F: Apply[F], A: Semigroup[A], B: Semigroup[B]): XorT[F, A, B] =
/**
* Similar to [[Xor.combine]] but mapped over an `F` context.
*
* Examples:
* {{{
* scala> import cats.data.XorT
* scala> import cats.implicits._
* scala> val l1: XorT[Option, String, Int] = XorT.left(Some("error 1"))
* scala> val l2: XorT[Option, String, Int] = XorT.left(Some("error 2"))
* scala> val r3: XorT[Option, String, Int] = XorT.right(Some(3))
* scala> val r4: XorT[Option, String, Int] = XorT.right(Some(4))
* scala> val noneXorT: XorT[Option, String, Int] = XorT.left(None)
*
* scala> l1 combine l2
* res0: XorT[Option, String, Int] = XorT(Some(Left(error 1)))
*
* scala> l1 combine r3
* res1: XorT[Option, String, Int] = XorT(Some(Left(error 1)))
*
* scala> r3 combine l1
* res2: XorT[Option, String, Int] = XorT(Some(Left(error 1)))
*
* scala> r3 combine r4
* res3: XorT[Option, String, Int] = XorT(Some(Right(7)))
*
* scala> l1 combine noneXorT
* res4: XorT[Option, String, Int] = XorT(None)
*
* scala> noneXorT combine l1
* res5: XorT[Option, String, Int] = XorT(None)
*
* scala> r3 combine noneXorT
* res6: XorT[Option, String, Int] = XorT(None)
*
* scala> noneXorT combine r4
* res7: XorT[Option, String, Int] = XorT(None)
* }}}
*/
def combine(that: XorT[F, A, B])(implicit F: Apply[F], B: Semigroup[B]): XorT[F, A, B] =
XorT(F.map2(this.value, that.value)(_ combine _))

def toValidated(implicit F: Functor[F]): F[Validated[A, B]] =
Expand Down