From 2e4b52f66426f3d4a8372526742f8c1b09edffd7 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 5 Jun 2020 15:44:19 -0400 Subject: [PATCH] Override map2Eval and combineKEval for monad transformers --- core/src/main/scala/cats/data/EitherT.scala | 6 +++++ core/src/main/scala/cats/data/Kleisli.scala | 8 +++++++ core/src/main/scala/cats/data/OptionT.scala | 6 +++++ core/src/main/scala/cats/data/WriterT.scala | 3 +++ .../test/scala/cats/tests/KleisliSuite.scala | 22 ++++++++++++++++--- 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 16f242a743..f921e40b37 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -1040,6 +1040,12 @@ private[data] trait EitherTSemigroupK[F[_], L] extends SemigroupK[EitherT[F, L, case l @ Left(_) => y.value case r @ Right(_) => F.pure(r) }) + + override def combineKEval[A](x: EitherT[F, L, A], y: Eval[EitherT[F, L, A]]): Eval[EitherT[F, L, A]] = + Eval.now(EitherT(F.flatMap(x.value) { + case l @ Left(_) => y.value.value + case r @ Right(_) => F.pure(r: Either[L, A]) + })) } private[data] trait EitherTFunctor[F[_], L] extends Functor[EitherT[F, L, *]] { diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index d88556791a..dd3afc56b8 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -541,6 +541,9 @@ sealed private[data] trait KleisliSemigroupK[F[_], A] extends SemigroupK[Kleisli override def combineK[B](x: Kleisli[F, A, B], y: Kleisli[F, A, B]): Kleisli[F, A, B] = Kleisli(a => F.combineK(x.run(a), y.run(a))) + + override def combineKEval[B](x: Kleisli[F, A, B], y: Eval[Kleisli[F, A, B]]): Eval[Kleisli[F, A, B]] = + Eval.now(Kleisli(a => F.combineKEval(x.run(a), y.map(_.run(a))).value)) } sealed private[data] trait KleisliMonoidK[F[_], A] extends MonoidK[Kleisli[F, A, *]] with KleisliSemigroupK[F, A] { @@ -622,6 +625,11 @@ private[data] trait KleisliApply[F[_], A] extends Apply[Kleisli[F, A, *]] with K override def ap[B, C](f: Kleisli[F, A, B => C])(fa: Kleisli[F, A, B]): Kleisli[F, A, C] = fa.ap(f) + override def map2Eval[B, C, Z](fa: Kleisli[F, A, B], fb: Eval[Kleisli[F, A, C]])( + f: (B, C) => Z + ): Eval[Kleisli[F, A, Z]] = + Eval.now(Kleisli(a => F.map2Eval(fa.run(a), fb.map(_.run(a)))(f).value)) + override def product[B, C](fb: Kleisli[F, A, B], fc: Kleisli[F, A, C]): Kleisli[F, A, (B, C)] = Kleisli(a => F.product(fb.run(a), fc.run(a))) } diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 16cff75a22..6c57e10560 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -540,6 +540,12 @@ private[data] trait OptionTSemigroupK[F[_]] extends SemigroupK[OptionT[F, *]] { implicit def F: Monad[F] def combineK[A](x: OptionT[F, A], y: OptionT[F, A]): OptionT[F, A] = x.orElse(y) + + override def combineKEval[A](x: OptionT[F, A], y: Eval[OptionT[F, A]]): Eval[OptionT[F, A]] = + Eval.now(OptionT(F.flatMap(x.value) { + case oa @ Some(_) => F.pure(oa) + case None => y.value.value + })) } private[data] trait OptionTMonoidK[F[_]] extends MonoidK[OptionT[F, *]] with OptionTSemigroupK[F] { diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index cf3da05942..bb309a9c74 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -704,6 +704,9 @@ sealed private[data] trait WriterTSemigroupK[F[_], L] extends SemigroupK[WriterT def combineK[A](x: WriterT[F, L, A], y: WriterT[F, L, A]): WriterT[F, L, A] = WriterT(F0.combineK(x.run, y.run)) + + override def combineKEval[A](x: WriterT[F, L, A], y: Eval[WriterT[F, L, A]]): Eval[WriterT[F, L, A]] = + F0.combineKEval(x.run, y.map(_.run)).map(WriterT(_)) } sealed private[data] trait WriterTMonoidK[F[_], L] extends MonoidK[WriterT[F, L, *]] with WriterTSemigroupK[F, L] { diff --git a/tests/src/test/scala/cats/tests/KleisliSuite.scala b/tests/src/test/scala/cats/tests/KleisliSuite.scala index 91c269c9e6..6a59890b02 100644 --- a/tests/src/test/scala/cats/tests/KleisliSuite.scala +++ b/tests/src/test/scala/cats/tests/KleisliSuite.scala @@ -9,9 +9,7 @@ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import cats.laws.discipline.SemigroupalTests.Isomorphisms import cats.laws.discipline.{DeferTests, MonoidKTests, SemigroupKTests} -import cats.syntax.flatMap._ -import cats.syntax.functor._ -import cats.syntax.traverse._ +import cats.implicits._ import cats.platform.Platform import cats.tests.Helpers.CSemi @@ -321,6 +319,24 @@ class KleisliSuite extends CatsSuite { result.run(()).value } + test("map2Eval is lazy") { + var count = 0 + val l = Kleisli { (n: Int) => count += 1; Option.empty[String] } + + l.map2Eval(Eval.now(l))(_ + _).value.run(0) + + count shouldBe 1 + } + + test("combineKEval is lazy") { + var count = 0 + val l = Kleisli { (n: Int) => count += 1; Option(n.toString) } + + l.combineKEval(Eval.now(l)).value.run(0) + + count shouldBe 1 + } + test("auto contravariant") { trait A1 trait A2