diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 52045404642..d106d82bf4c 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -293,6 +293,27 @@ private[data] trait EitherTFunctions { def apply[E, A](opt: Option[A], ifNone: => E)(implicit F: Applicative[F]): EitherT[F, E, A] = EitherT(F.pure(Either.fromOption(opt, ifNone))) } + + /** If the condition is satisfied, return the given `A` in `Right` + * lifted into the specified `Applicative`, otherwise, return the + * given `E` in `Left` lifted into the specified `Applicative`. + * + * {{{ + * scala> val userInput = "hello world" + * scala> EitherT.cond[Future]( + * userInput.forall(_.isDigit) && userInput.size == 10, + * userInput, + * s"The input does not look like a phone number") + * res0: EitherT[Future,String,String] = + * EitherT(Future(Success(Left(The input hello world does not look like a phone number)))) + * }}} + */ + final def cond[F[_]]: CondPartiallyApplied[F] = new CondPartiallyApplied + + final class CondPartiallyApplied[F[_]] private[EitherTFunctions] { + def apply[E, A](test: Boolean, right: => A, left: => E)(implicit F: Applicative[F]): EitherT[F, E, A] = + EitherT(F.pure(Either.cond(test, right, left))) + } } private[data] abstract class EitherTInstances extends EitherTInstances1 { diff --git a/tests/src/test/scala/cats/tests/EitherTTests.scala b/tests/src/test/scala/cats/tests/EitherTTests.scala index ce8d7f1bc31..73312c9d12a 100644 --- a/tests/src/test/scala/cats/tests/EitherTTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTTests.scala @@ -156,6 +156,12 @@ class EitherTTests extends CatsSuite { } } + test("cond") { + forAll { (cond: Boolean, s: String, i: Int) => + Either.cond(cond, s, i) should === (EitherT.cond[Id](cond, s, i).value) + } + } + test("isLeft negation of isRight") { forAll { (eithert: EitherT[List, String, Int]) => eithert.isLeft should === (eithert.isRight.map(! _))