diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 3669119de7..b9ada5941c 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -171,6 +171,10 @@ private[data] abstract class XorTInstances extends XorTInstances1 { } } + implicit def xorTTraverse[F[_], L](implicit F: Traverse[F]): Traverse[XorT[F, L, ?]] = + new XorTTraverse[F, L] { + val F0: Traverse[F] = F + } } private[data] abstract class XorTInstances1 extends XorTInstances2 { @@ -191,6 +195,11 @@ private[data] abstract class XorTInstances1 extends XorTInstances2 { def empty[A]: XorT[F, L, A] = XorT.left(F.pure(L.empty))(F) } } + + implicit def xorTFoldable[F[_], L](implicit F: Foldable[F]): Foldable[XorT[F, L, ?]] = + new XorTFoldable[F, L] { + val F0: Foldable[F] = F + } } private[data] abstract class XorTInstances2 extends XorTInstances3 { @@ -264,4 +273,19 @@ private[data] trait XorTMonadCombine[F[_], L] extends MonadCombine[XorT[F, L, ?] implicit val L: Monoid[L] } +private[data] sealed trait XorTFoldable[F[_], L] extends Foldable[XorT[F, L, ?]] { + implicit def F0: Foldable[F] + + def foldLeft[A, B](fa: XorT[F, L, A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b)(f) + + def foldRight[A, B](fa: XorT[F, L, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.foldRight(lb)(f) +} + +private[data] sealed trait XorTTraverse[F[_], L] extends Traverse[XorT[F, L, ?]] with XorTFoldable[F, L] { + override implicit def F0: Traverse[F] + override def traverse[G[_]: Applicative, A, B](fa: XorT[F, L, A])(f: A => G[B]): G[XorT[F, L, B]] = + fa traverse f +} diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index bafd721bc3..dede67e5c4 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -1,18 +1,38 @@ package cats package tests +import cats.functor.Bifunctor import cats.data.{Xor, XorT} -import cats.laws.discipline.{BifunctorTests, MonadErrorTests, MonoidKTests, SerializableTests} +import cats.laws.discipline.{BifunctorTests, FoldableTests, FunctorTests, MonadErrorTests, MonoidKTests, SerializableTests, TraverseTests} import cats.laws.discipline.arbitrary._ - class XorTTests extends CatsSuite { implicit val eq0 = XorT.xorTEq[List, String, String Xor Int] implicit val eq1 = XorT.xorTEq[XorT[List, String, ?], String, Int](eq0) checkAll("XorT[List, String, Int]", MonadErrorTests[XorT[List, String, ?], String].monadError[Int, Int, Int]) - checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) checkAll("MonadError[XorT[List, ?, ?]]", SerializableTests.serializable(MonadError[XorT[List, String, ?], String])) + checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) + checkAll("MonoidK[XorT[List, String, ?]]", SerializableTests.serializable(MonoidK[XorT[List, String, ?]])) checkAll("XorT[List, ?, ?]", BifunctorTests[XorT[List, ?, ?]].bifunctor[Int, Int, Int, String, String, String]) + checkAll("Bifunctor[XorT[List, ?, ?]]", SerializableTests.serializable(Bifunctor[XorT[List, ?, ?]])) + checkAll("XorT[List, Int, ?]", TraverseTests[XorT[List, Int, ?]].foldable[Int, Int]) + checkAll("Traverse[XorT[List, Int, ?]]", SerializableTests.serializable(Traverse[XorT[List, Int, ?]])) + + { + implicit val F = ListWrapper.foldable + checkAll("XorT[ListWrapper, Int, ?]", FoldableTests[XorT[ListWrapper, Int, ?]].foldable[Int, Int]) + checkAll("Foldable[XorT[ListWrapper, Int, ?]]", SerializableTests.serializable(Foldable[XorT[ListWrapper, Int, ?]])) + } + + { + implicit val F = ListWrapper.functor + checkAll("XorT[ListWrapper, Int, ?]", FunctorTests[XorT[ListWrapper, Int, ?]].functor[Int, Int, Int]) + checkAll("Functor[XorT[ListWrapper, Int, ?]]", SerializableTests.serializable(Functor[XorT[ListWrapper, Int, ?]])) + } + + // make sure that the Monad and Traverse instances don't result in ambiguous + // Functor instances + Functor[XorT[List, Int, ?]] test("toValidated") { forAll { (xort: XorT[List, String, Int]) =>