diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 99ab2284dc..d5bcc4c767 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -447,6 +447,12 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { } }) + def semiflatTap[C](f: B => F[C])(implicit F: Monad[F]): EitherT[F, A, B] = + semiflatMap(b => F.as(f(b), b)) + + def leftSemiflatTap[C](f: A => F[C])(implicit F: Monad[F]): EitherT[F, A, B] = + leftSemiflatMap(a => F.as(f(a), a)) + def compare(that: EitherT[F, A, B])(implicit o: Order[F[Either[A, B]]]): Int = o.compare(value, that.value) diff --git a/tests/src/test/scala/cats/tests/EitherTSuite.scala b/tests/src/test/scala/cats/tests/EitherTSuite.scala index 23f1bd002c..96d97c9526 100644 --- a/tests/src/test/scala/cats/tests/EitherTSuite.scala +++ b/tests/src/test/scala/cats/tests/EitherTSuite.scala @@ -1,13 +1,12 @@ package cats package tests -import cats.Bifunctor -import cats.data.EitherT - +import cats.data.{EitherT, State} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.SemigroupalTests.Isomorphisms import cats.kernel.laws.discipline.{EqTests, MonoidTests, OrderTests, PartialOrderTests, SemigroupTests} + import scala.util.{Failure, Success, Try} class EitherTSuite extends CatsSuite { @@ -541,6 +540,43 @@ class EitherTSuite extends CatsSuite { } } + test("semiflatTap does not change the return value") { + type TestEffect[A] = State[List[Int], A] + forAll { (eithert: EitherT[TestEffect, String, Int], f: Int => TestEffect[Int], initial: List[Int]) => + eithert.semiflatTap(v => f(v)).value.runA(initial) should ===(eithert.value.runA(initial)) + } + } + + test("semiflatTap runs the effect") { + type TestEffect[A] = State[List[Int], A] + forAll { (eithert: EitherT[TestEffect, String, Int], f: Int => TestEffect[Int], initial: List[Int]) => + eithert.semiflatTap(v => f(v)).value.runS(initial) should ===(eithert.semiflatMap(f).value.runS(initial)) + } + } + + test("leftSemiflatTap does not change the return value") { + type TestEffect[A] = State[List[Int], A] + forAll { (eithert: EitherT[TestEffect, String, Int], f: String => TestEffect[Int], initial: List[Int]) => + eithert.leftSemiflatTap(v => f(v)).value.runA(initial) should ===(eithert.value.runA(initial)) + } + } + + test("leftSemiflatTap runs the effect") { + type TestEffect[A] = State[List[Int], A] + forAll { (eithert: EitherT[TestEffect, String, Int], f: String => TestEffect[Int], initial: List[Int]) => + eithert.leftSemiflatTap(v => f(v)).value.runS(initial) should ===(eithert.leftSemiflatMap(f).value.runS(initial)) + } + } + + test("leftSemiflatTap consistent with swap and the semiflatTap") { + type TestEffect[A] = State[List[Int], A] + forAll { (eithert: EitherT[TestEffect, String, Int], f: String => TestEffect[Int], initial: List[Int]) => + eithert.leftSemiflatTap(v => f(v)).value.runA(initial) should ===( + eithert.swap.semiflatTap(v => f(v)).swap.value.runA(initial) + ) + } + } + test("biSemiflatMap consistent with leftSemiflatMap and semiFlatmap") { forAll { (eithert: EitherT[List, String, Int], fa: String => List[Int], fb: Int => List[String]) => eithert.biSemiflatMap(fa, fb) should ===(eithert.leftSemiflatMap(fa).semiflatMap(fb))