diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 748c1b029b..90a79138ed 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -120,6 +120,24 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { case r@Right(_) => F.pure(r.leftCast) }) + /** Combine `leftSemiflatMap` and `semiflatMap` together. + * + * Example: + * {{{ + * scala> import cats.implicits._ + * scala> import cats.data.EitherT + * + * scala> val eitherT: EitherT[List, String, Int] = EitherT[List, String, Int](List(Left("abc"), Right(123))) + * scala> eitherT.biSemiflatMap(string => List(string.length), int => List(int.toFloat)) + * res0: cats.data.EitherT[List,Int,Float] = EitherT(List(Left(3), Right(123.0))) + * }}} + */ + def biSemiflatMap[C, D](fa: A => F[C], fb: B => F[D])(implicit F: Monad[F]): EitherT[F, C, D] = + EitherT(F.flatMap(value) { + case Left(a) => F.map(fa(a)) { c => Left(c) } + case Right(b) => F.map(fb(b)) { d => Right(d) } + }) + 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 b3063bddbf..dd07e45f4f 100644 --- a/tests/src/test/scala/cats/tests/EitherTSuite.scala +++ b/tests/src/test/scala/cats/tests/EitherTSuite.scala @@ -471,4 +471,22 @@ class EitherTSuite extends CatsSuite { } } + 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)) + } + } + + test("biSemiflatMap consistent with leftSemiflatMap") { + forAll { (eithert: EitherT[List, String, Int], fa: String => List[Int]) => + eithert.biSemiflatMap(fa, List(_)) should ===(eithert.leftSemiflatMap(a => fa(a))) + } + } + + test("biSemiflatMap consistent with semiflatMap") { + forAll { (eithert: EitherT[List, String, Int], fb: Int => List[String]) => + eithert.biSemiflatMap(List(_), fb) should ===(eithert.semiflatMap(b => fb(b))) + } + } + }