From c5ba3b244c63fd0d022913b7524f0a79dceb97d5 Mon Sep 17 00:00:00 2001 From: Jesper Engberg Date: Sat, 27 Jan 2024 23:17:54 +0100 Subject: [PATCH 1/2] feat: add `.toEitherT` to `Validated` --- core/src/main/scala/cats/data/Validated.scala | 24 +++++++++++++++++++ docs/datatypes/validated.md | 12 +++++----- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 5faf9a970c..3cec8f6a8f 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -265,6 +265,30 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { case Valid(a) => Right(a) } + /** + * Converts the value to an Either[F, E, A] + * + * Example: + * {{{ + * scala> import cats.syntax.all._ + * scala> given ExecutionContext = ExecutionContext.global + * + * scala> val v1 = "error".invalid[Int] + * scala> val v2 = 123.valid[String] + * + * scala> v1.toEitherT[Future] + * res0: EitherT[Future, String, Int] = EitherT(Future(Left(error))) + * + * scala> v2.toEither + * res1: EitherT[Future, String, Int] = EitherT(Future(Right(123))) + * }}} + */ + def toEitherT[F[_]: Applicative]: EitherT[F, E, A] = + this match { + case Invalid(e) => EitherT.leftT(e) + case Valid(a) => EitherT.rightT(a) + } + /** * Returns Valid values wrapped in Some, and None for Invalid values * diff --git a/docs/datatypes/validated.md b/docs/datatypes/validated.md index 60fb9532d2..c0aa1dcd72 100644 --- a/docs/datatypes/validated.md +++ b/docs/datatypes/validated.md @@ -298,9 +298,9 @@ But, what about if we want _another_ way of combining? We can provide our custom Cats offers you a nice set of combinators for transforming your `Validated` based approach to an `Either` one and vice-versa. We've used `.toValidated` in our second example, now let's see how to use `.toEither`. -#### From `Validated` to `Either` +#### From `Validated` to `Either`/`EitherT` -To do this, simply use `.toEither` combinator: +To do this, simply use `.toEither`/`.toEitherT` combinator: ```scala mdoc // Successful case @@ -319,7 +319,7 @@ FormValidatorNec.validateForm( firstName = "John", lastName = "Doe", age = 5 -).toEither +).toEitherT[Future] ``` With this conversion, as you can see, we got an `Either` with a `NonEmptyChain` detailing the possible validation errors or our `RegistrationData` object. @@ -591,7 +591,7 @@ validatedMonad.tuple2(Validated.invalidNec[String, Int]("oops"), Validated.inval ``` This one short circuits! Therefore, if we were to define a `Monad` (or `FlatMap`) instance for `Validated` we would -have to override `ap` to get the behavior we want. +have to override `ap` to get the behavior we want. ```scala mdoc:silent:nest import cats.Monad @@ -629,14 +629,14 @@ But then the behavior of `flatMap` would be inconsistent with that of `ap`, and ```scala // the `<->` operator means "is equivalent to" and returns a data structure // `IsEq` that is used to verify the equivalence of the two expressions -def flatMapConsistentApply[A, B](fa: F[A], fab: F[A => B]): IsEq[F[B]] = +def flatMapConsistentApply[A, B](fa: F[A], fab: F[A => B]): IsEq[F[B]] = fab.ap(fa) <-> fab.flatMap(f => fa.map(f)) ``` ```scala mdoc:silent import cats.laws._ -val flatMapLawsForAccumulatingValidatedMonad = +val flatMapLawsForAccumulatingValidatedMonad = FlatMapLaws[Validated[NonEmptyChain[String], *]](accumulatingValidatedMonad) val fa = Validated.invalidNec[String, Int]("oops") From b16951cbf700b0b6c3e2c951949763727f118816 Mon Sep 17 00:00:00 2001 From: Jesper Engberg Date: Sat, 27 Jan 2024 23:21:37 +0100 Subject: [PATCH 2/2] fix: polish comment a bit --- core/src/main/scala/cats/data/Validated.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 3cec8f6a8f..5511dca2f4 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -266,7 +266,7 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { } /** - * Converts the value to an Either[F, E, A] + * Converts the value to an EitherT[F, E, A] * * Example: * {{{ @@ -276,11 +276,11 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { * scala> val v1 = "error".invalid[Int] * scala> val v2 = 123.valid[String] * - * scala> v1.toEitherT[Future] - * res0: EitherT[Future, String, Int] = EitherT(Future(Left(error))) + * scala> v1.toEitherT[Future].value + * res0: Future[Either[String, Int]] = Future(Left(error)) * - * scala> v2.toEither - * res1: EitherT[Future, String, Int] = EitherT(Future(Right(123))) + * scala> v2.toEitherT[Future].value + * res1: Future[Either[String, Int]] = Future(Right(123)) * }}} */ def toEitherT[F[_]: Applicative]: EitherT[F, E, A] =