From c6a457b1de4d1e73b4249679bbc064a4377951c8 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sat, 5 Dec 2015 11:23:58 +0000 Subject: [PATCH] Adds Monoid instance for Validated --- core/src/main/scala/cats/data/Validated.scala | 22 ++++++++++++++++++- .../scala/cats/tests/ValidatedTests.scala | 11 +++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index f56c6ee196..160d29e4e8 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -168,6 +168,21 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { case Valid(a) => f(a) case i @ Invalid(_) => i } + + /** + * Combine this `Validated` with another `Validated`, using the `Semigroup` + * instances of the underlying `E` and `A` instances. The resultant `Validated` + * will be `Valid`, if, and only if, both this `Validated` instance and the + * supplied `Validated` instance are also `Valid`. + */ + def combine[EE >: E, AA >: A](that: Validated[EE, AA])(implicit EE: Semigroup[EE], AA: Semigroup[AA]): Validated[EE, AA] = + (this, that) match { + case (Valid(a), Valid(b)) => Valid(AA.combine(a, b)) + case (Invalid(a), Invalid(b)) => Invalid(EE.combine(a, b)) + case (Invalid(_), _) => this + case _ => that + } + } object Validated extends ValidatedInstances with ValidatedFunctions{ @@ -177,6 +192,12 @@ object Validated extends ValidatedInstances with ValidatedFunctions{ private[data] sealed abstract class ValidatedInstances extends ValidatedInstances1 { + + implicit def validatedMonoid[A, B](implicit A: Semigroup[A], B: Monoid[B]): Monoid[Validated[A, B]] = new Monoid[Validated[A, B]] { + def empty: Validated[A, B] = Valid(B.empty) + def combine(x: Validated[A, B], y: Validated[A, B]): Validated[A, B] = x combine y + } + implicit def validatedOrder[A: Order, B: Order]: Order[Validated[A,B]] = new Order[Validated[A,B]] { def compare(x: Validated[A,B], y: Validated[A,B]): Int = x compare y override def partialCompare(x: Validated[A,B], y: Validated[A,B]): Double = x partialCompare y @@ -282,4 +303,3 @@ trait ValidatedFunctions { */ def fromOption[A, B](o: Option[B], ifNone: => A): Validated[A,B] = o.fold(invalid[A, B](ifNone))(valid) } - diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 1a99d337e7..ba88b5337f 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -7,7 +7,7 @@ import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeTests, Se import org.scalacheck.{Gen, Arbitrary} import org.scalacheck.Arbitrary._ import cats.laws.discipline.arbitrary._ -import algebra.laws.OrderLaws +import algebra.laws.{OrderLaws, GroupLaws} import scala.util.Try @@ -22,6 +22,8 @@ class ValidatedTests extends CatsSuite { checkAll("Validated[String, Int]", OrderLaws[Validated[String, Int]].order) checkAll("Order[Validated[String, Int]]", SerializableTests.serializable(Order[Validated[String, Int]])) + checkAll("Monoid[Validated[String, Int]]", GroupLaws[Validated[String, Int]].monoid) + { implicit val S = ListWrapper.partialOrder[String] implicit val I = ListWrapper.partialOrder[Int] @@ -143,4 +145,11 @@ class ValidatedTests extends CatsSuite { Validated.fromOption(o, s).toOption should === (o) } } + + test("isValid after combine, iff both are valid") { + forAll { (lhs: Validated[Int, String], rhs: Validated[Int, String]) => + lhs.combine(rhs).isValid should === (lhs.isValid && rhs.isValid) + } + } + }