From 91350357ca23f3c9f61552359b3a1f78964ad71b Mon Sep 17 00:00:00 2001 From: "Kai(luo) Wang" Date: Fri, 2 Mar 2018 15:10:50 -0500 Subject: [PATCH] added liftTo to Try Either and Option (#2179) --- .../main/scala/cats/ApplicativeError.scala | 26 +++++++++++++++ .../main/scala/cats/syntax/TrySyntax.scala | 32 ++++++++++++++++++ core/src/main/scala/cats/syntax/all.scala | 2 ++ .../scala/cats/syntax/applicativeError.scala | 33 +++++++++++++++++++ core/src/main/scala/cats/syntax/either.scala | 13 ++++++++ core/src/main/scala/cats/syntax/option.scala | 25 ++++++++++++++ 6 files changed, 131 insertions(+) create mode 100644 core/src/main/scala/cats/syntax/TrySyntax.scala diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala index 684ac7ace4..b450ee30f9 100644 --- a/core/src/main/scala/cats/ApplicativeError.scala +++ b/core/src/main/scala/cats/ApplicativeError.scala @@ -180,4 +180,30 @@ trait ApplicativeError[F[_], E] extends Applicative[F] { object ApplicativeError { def apply[F[_], E](implicit F: ApplicativeError[F, E]): ApplicativeError[F, E] = F + + private[cats] final class LiftFromOptionPartially[F[_]](val dummy: Boolean = true) extends AnyVal { + def apply[E, A](oa: Option[A], ifEmpty: => E)(implicit F: ApplicativeError[F, E]): F[A] = + oa match { + case Some(a) => F.pure(a) + case None => F.raiseError(ifEmpty) + } + } + + + /** + * lift from scala.Option[A] to a F[A] + * + * Example: + * {{{ + * scala> import cats.implicits._ + * scala> import cats.ApplicativeError + * + * scala> ApplicativeError.liftFromOption[Either[String, ?]](Some(1), "Empty") + * res0: scala.Either[String, Int] = Right(1) + * + * scala> ApplicativeError.liftFromOption[Either[String, ?]](Option.empty[Int], "Empty") + * res1: scala.Either[String, Int] = Left(Empty) + * }}} + */ + def liftFromOption[F[_]]: LiftFromOptionPartially[F] = new LiftFromOptionPartially[F] } diff --git a/core/src/main/scala/cats/syntax/TrySyntax.scala b/core/src/main/scala/cats/syntax/TrySyntax.scala new file mode 100644 index 0000000000..29c786c229 --- /dev/null +++ b/core/src/main/scala/cats/syntax/TrySyntax.scala @@ -0,0 +1,32 @@ +package cats +package syntax + +import scala.util.Try + +trait TrySyntax { + implicit final def catsSyntaxTry[A](t: Try[A]): TryOps[A] = new TryOps(t) +} + + +final class TryOps[A](val self: Try[A]) extends AnyVal { + + /** + * lift the `try` into a `F[_]` with `ApplicativeError[F, Throwable]` instance + * + * {{{ + * scala> import cats.implicits._ + * scala> import util.Try + * + * scala> val s: Try[Int] = Try(3) + * scala> s.liftTo[Either[Throwable, ?]] + * res0: Either[Throwable, Int] = Right(3) + * + * scala> val f: Try[Int] = Try(throw new Throwable("boo")) + * scala> f.liftTo[Either[Throwable, ?]] + * res0: Either[Throwable, Int] = Left(java.lang.Throwable: boo) + * }}} + */ + def liftTo[F[_]](implicit F: ApplicativeError[F, Throwable]): F[A] = + F.fromTry(self) + +} diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 926a0a7c47..2b493b7ae6 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -55,3 +55,5 @@ trait AllSyntax trait AllSyntaxBinCompat0 extends UnorderedTraverseSyntax + with ApplicativeErrorExtension + with TrySyntax diff --git a/core/src/main/scala/cats/syntax/applicativeError.scala b/core/src/main/scala/cats/syntax/applicativeError.scala index b2b63798eb..438ab63128 100644 --- a/core/src/main/scala/cats/syntax/applicativeError.scala +++ b/core/src/main/scala/cats/syntax/applicativeError.scala @@ -11,6 +11,39 @@ trait ApplicativeErrorSyntax { new ApplicativeErrorOps[F, E, A](fa) } +/** + * Extension to ApplicativeError in a binary compat way + */ +trait ApplicativeErrorExtension { + implicit final def catsSyntaxApplicativeErrorExtension[F[_], E](F: ApplicativeError[F, E]): + ApplicativeErrorExtensionOps[F, E] = + new ApplicativeErrorExtensionOps(F) +} + +final class ApplicativeErrorExtensionOps[F[_], E](F: ApplicativeError[F, E]) { + + + /** + * Convert from scala.Option + * + * Example: + * {{{ + * scala> import cats.implicits._ + * scala> import cats.ApplicativeError + * scala> val F = ApplicativeError[Either[String, ?], String] + * + * scala> F.fromOption(Some(1), "Empty") + * res0: scala.Either[String, Int] = Right(1) + * + * scala> F.fromOption(Option.empty[Int], "Empty") + * res1: scala.Either[String, Int] = Left(Empty) + * }}} + */ + def fromOption[A](oa: Option[A], ifEmpty: => E): F[A] = + ApplicativeError.liftFromOption(oa, ifEmpty)(F) + +} + final class ApplicativeErrorIdOps[E](val e: E) extends AnyVal { def raiseError[F[_], A](implicit F: ApplicativeError[F, E]): F[A] = F.raiseError(e) diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index dc290d5d4a..40bd310652 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -269,8 +269,21 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { def raiseOrPure[F[_]](implicit ev: ApplicativeError[F, A]): F[B] = ev.fromEither(eab) + /** + * lift the `Either` into a `F[_]` with `ApplicativeError[F, A]` instance + * + * {{{ + * scala> import cats.implicits._ + * scala> import cats.data.EitherT + * scala> val e: Either[String, Int] = Right(3) + * scala> e.liftTo[EitherT[Option, String, ?]] + * res0: cats.data.EitherT[Option, String, Int] = EitherT(Some(Right(3))) + * }}} + */ + def liftTo[F[_]](implicit F: ApplicativeError[F, A]): F[B] = F.fromEither(eab) } + final class EitherObjectOps(val either: Either.type) extends AnyVal { // scalastyle:off ensure.single.space.after.token def left[A, B](a: A): Either[A, B] = Left(a) diff --git a/core/src/main/scala/cats/syntax/option.scala b/core/src/main/scala/cats/syntax/option.scala index 25dbbffb07..b251127f39 100644 --- a/core/src/main/scala/cats/syntax/option.scala +++ b/core/src/main/scala/cats/syntax/option.scala @@ -2,6 +2,7 @@ package cats package syntax import cats.data.{Ior, Validated, ValidatedNel} +import cats.syntax.OptionOps.LiftToPartiallyApplied trait OptionSyntax { final def none[A]: Option[A] = Option.empty[A] @@ -170,4 +171,28 @@ final class OptionOps[A](val oa: Option[A]) extends AnyVal { * }}} */ def orEmpty(implicit A: Monoid[A]): A = oa.getOrElse(A.empty) + + /** + * Lift to a F[A] as long as it has an ApplicativeError[F] instance + * + * Example: + * {{{ + * scala> import cats.implicits._ + * + * scala> Some(1).liftTo[Either[String, ?]]("Empty") + * res0: scala.Either[String, Int] = Right(1) + * + * scala> Option.empty[Int].liftTo[Either[String, ?]]("Empty") + * res1: scala.Either[String, Int] = Left(Empty) + * }}} + */ + def liftTo[F[_]]: LiftToPartiallyApplied[F, A] = new LiftToPartiallyApplied(oa) + +} + +object OptionOps { + private[syntax] final class LiftToPartiallyApplied[F[_], A](oa: Option[A]) { + def apply[E](ifEmpty: => E)(implicit F: ApplicativeError[F, E]): F[A] = + ApplicativeError.liftFromOption(oa, ifEmpty) + } }