diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index a4361545c0..4c1ca96015 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -194,6 +194,12 @@ final class EitherOps[A, B](private val eab: Either[A, B]) extends AnyVal { case r @ Right(_) => EitherUtil.leftCast(r) } + def leftMapOrKeep[AA >: A](pf: PartialFunction[A, AA]): Either[AA, B] = + eab match { + case Left(a) => Left(pf.applyOrElse(a, identity[AA])) + case r: Right[A, B] => r + } + @deprecated("Included in the standard library", "2.1.0-RC1") private[syntax] def flatMap[AA >: A, D](f: B => Either[AA, D]): Either[AA, D] = eab match { @@ -207,6 +213,12 @@ final class EitherOps[A, B](private val eab: Either[A, B]) extends AnyVal { case r @ Right(_) => EitherUtil.leftCast(r) } + def leftFlatMapOrKeep[AA >: A, BB >: B](pfa: PartialFunction[A, Either[AA, BB]]): Either[AA, BB] = + eab match { + case l @ Left(a) => pfa.applyOrElse(a, (_: A) => l) + case r: Right[A, B] => r + } + def compare[AA >: A, BB >: B](that: Either[AA, BB])(implicit AA: Order[AA], BB: Order[BB]): Int = eab match { case Left(a1) => diff --git a/tests/shared/src/test/scala/cats/tests/EitherSuite.scala b/tests/shared/src/test/scala/cats/tests/EitherSuite.scala index eca7b226e7..3d0e6cac73 100644 --- a/tests/shared/src/test/scala/cats/tests/EitherSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/EitherSuite.scala @@ -415,6 +415,25 @@ class EitherSuite extends CatsSuite { } } + test("leftFlatMapOrKeep consistent with leftMapOrKeep") { + forAll { (either: Either[String, Int], pf: PartialFunction[String, String]) => + val liftedPF: PartialFunction[String, Either[String, Int]] = { case a => + Either.left[String, Int](pf.applyOrElse(a, identity[String])) + } + assert(either.leftFlatMapOrKeep(liftedPF) === either.leftMapOrKeep(pf)) + } + } + + test("leftFlatMapOrKeep consistent with swap and then flatMapOrKeep") { + import cats.syntax.monad._ + + forAll { (either: Either[String, Int], pf: PartialFunction[String, Either[String, Int]]) => + assert(either.leftFlatMapOrKeep(pf) === either.swap.flatMapOrKeep { case a => + pf.applyOrElse(a, (_: String) => either).swap + }.swap) + } + } + test("raiseWhen raises when true") { val result = Either.raiseWhen(true)("ok") assert(result === Left("ok"))