diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 54120d43fe..6cac188086 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -96,6 +96,18 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { def leftMap[C](f: A => C)(implicit F: Functor[F]): EitherT[F, C, B] = bimap(f, identity) + def leftFlatMap[BB >: B, D](f: A => EitherT[F, D, BB])(implicit F: Monad[F]): EitherT[F, D, BB] = + EitherT(F.flatMap(value) { + case Left(a) => f(a).value + case r@Right(_) => F.pure(r.leftCast) + }) + + def leftSemiflatMap[D](f: A => F[D])(implicit F: Monad[F]): EitherT[F, D, B] = + EitherT(F.flatMap(value) { + case Left(a) => F.map(f(a)) { d => Left(d) } + case r@Right(_) => F.pure(r.leftCast) + }) + 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/EitherTTests.scala b/tests/src/test/scala/cats/tests/EitherTTests.scala index 01663ee5ac..692e9d7411 100644 --- a/tests/src/test/scala/cats/tests/EitherTTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTTests.scala @@ -434,4 +434,16 @@ class EitherTTests extends CatsSuite { } yield s1 ++ s2 } + test("leftFlatMap") { + forAll { (eithert: EitherT[List, String, Int], f: String => String) => + eithert.leftFlatMap(v => EitherT.left[Int](List(f(v)))) should ===(eithert.leftMap(f)) + } + } + + test("leftSemiflatMap") { + forAll { (eithert: EitherT[List, String, Int], f: String => String) => + eithert.leftSemiflatMap(v => List(f(v))) should ===(eithert.leftMap(f)) + } + } + }