Scala, ZIO - how to convert EitherT to ZIO? - scala

I have a simple method signature with returning type as EitherT:
def run(someEither: Either[SomeException, Statement], id: String): EitherT[Future, EncodingException, ResultSet]
But higher level method has a signature:
def someMethod(...) : zio.IO[SomeException, SomeResult]
I call run method inside someMethod like this:
run(someEither, data.getOrElse("empty")).bimap(
{ e: SomeException => IO.fail(e) },
_ => IO.succeed(entity)
)
But I got compilation error about Type mismatch - required: IO, found: EitherT.
How it is possible to convert EitherT into zio.IO?

I think you could do something like:
import zio._
import cats.data.EitherT
import scala.concurrent.Future
type ResultSet = ???
val eitherT: EitherT[Future, Exception, ResultSet] = ???
val result: IO[Throwable, ResultSet] = ZIO.fromFuture(_ => eitherT.value).flatMap(ZIO.fromEither)

It depends on what your data looks like and how you want to handle failed futures, but here's a few options for if you don't care to handle specific kinds of Throwables from the Future:
// EitherT[Future, E, A] to IO[E, A]
val io: IO[E, A] =
ZIO.fromFuture(eitherT.value)
.orDie
.flatMap(ZIO.fromEither)
// EitherT[Future, Throwable, A] to Task[A]
val task: Task[A] =
ZIO
.fromFuture(_ => et.value)
.flatMap(ZIO.fromEither)
// EitherT[Future, Throwable, A] to UIO[A]
val task: UIO[A] =
ZIO
.fromFuture(_ => et.value)
.flatMap(ZIO.fromEither)
.orDie
Longer explanation
First off, there isn't a built-in for converting from EitherT, so you'll need to use the underlying Future, from which you can very quickly get to a ZIO Task:
val eitherT: EitherT[Future, A, B] = ???
val underlying: Future[Either[A, B]] = eitherT.value
// Note that this is equivalent to IO[Throwable, Either[...]]
val zioTask: Task[Either[MyError, MyResult]] = ZIO.fromFuture(_ => underlying)
// Or all at once:
val zioTask = ZIO.fromFuture(_ => eitherT.value)
I had a similar need as the OP, but in my case the Left is not an exception but rather my own MyError instead:
val underlying: Future[Either[MyError, MyResult]]
If you're in a similar boat, then you need to make a decision about what to do with any exceptions that might be in a possible failed future. One possible option is to...
Ignore them and let the fiber die if they happen
If the exception in the future represents a defect in the code, then often there is no other choice than to let the fiber die as it cannot proceed further. A fiber killed with orDie will have a Throwable representing the cause, but you have the option – which I recommend – to use your own exception that details how and why the failure occurred via orDieWith.
val uio: UIO[Either[MyError, MyResult]] = task.orDie
// Or
case class ExplanatoryException(cause: Throwable)
extends RuntimeException(
"A failure was encountered when we used XYZ-lib/made service call to FooBar/etc",
cause
)
val uio: UIO[Either[MyError, MyResult]] = task.orDieWith(ExplanatoryException)
// Then finally
val fullyConverted: IO[MyError, MyResult] = uio.flatMap(ZIO.fromEither)
Now from here you have a clean IO to work with!
Extension methods!
If you're doing this a lot you can add an extension method to EitherT to do the conversion for you:
implicit class EitherTOps[A, B](et: EitherT[Future, A, B]) {
def toIO: IO[A, B] = ZIO.fromFuture(_ => et.value)
.orDie
.flatMap(ZIO.fromEither)
def toIOWith(f: Throwable => Throwable): IO[A, B] = ZIO.fromFuture(_ => et.value)
.orDieWith(f)
.flatMap(ZIO.fromEither)
}
And beyond!
If you're able to constrain A <: Throwable then it makes it pretty easy to write some convenient toTask extension methods for EitherT, but I'll leave that as an exercise to the reader.
If you know a priori what kind of Throwable you may get from the Future and you want to actually handle it gracefully, then see this question which covers all the ways to do so.

Related

ZIO Propagate custom exception family within Future[Either[MyException, A]], without Task which is Throwable only

I'm looking to try out ZIO but I have an exception family that looks similar to this
trait MyException extends Exception {
def errorCode: Int
}
, functions that return Future[Either[MyException, A]] and the problem I'm running into is transforming these into ZIO land. e.g.
def foo(): Future[Either[MyException, A]] = ???
def process(): ZIO[Any, MyException, Unit] =
ZIO
.fromFuture(_ => foo())
.flatMap(ZIO.fromEither[MyException, Unit]) // compiler error, expects Throwable instead of MyException
because I know foo() can return MyException, I want that to reflect in the ZIO type but it appears the type is lost during the transformation
First of all foo should take in an ExecutionContext so that it can use the one provided by the ZIO runtime instead of any global ExecutionContext you might have:
def foo[A]()(implicit ec: ExecutionContext): Future[Either[MyException, A]] = ???
Because ZIO still expects Future to still be able to fail with any Throwable and not just MyException, either you widen the error of process to Throwable, like:
def process(): ZIO[Any, Throwable, Unit] =
ZIO
.fromFuture(implicit ec => foo())
.flatMap(ZIO.fromEither[MyException, Unit](_)) //`fromEither` takes in an impicit in ZIO 2 so we need to be more explicit with the syntax
or you will still need to handle the Throwable with a handler like catchAll:
def process(): ZIO[Any, MyException, Unit] =
ZIO
.fromFuture(implicit ec => foo())
.catchAll {
//it depends on how you want to handle the `Throwable` as long as you only fail with `MyException`
case e: MyException => ZIO.fail(e)
case _ => ZIO.succeed(Right())
}.flatMap(ZIO.fromEither[MyException, Unit](_))
without Task which is Throwable only
I think you might be confusing the contravariant ZIO environment with the covariant error type parameter. The process you want can fail with MyException only but the lifted Future within can fail with any Throwable including MyException. You can read more about variances here.

Composing Multiple Futures and Option in Scala with ZIO

I just started evaluating ZIO to improve the programming model and the performance of my asynchronous Scala code. In my code base I deal with Future[Option[T]] often, and to this point I have dealt with that using Scalaz's OptionT monad transformer. Now I want to try this with ZIO.
Consider two functions:
def foo: String => Future[Option[T]]
and
def bar: T => Future[U]
I tried something like this:
val t = for {
o: Option[Int] <- ZIO.fromFuture { implicit ec =>
foo("test")
}
i: Int <- ZIO.fromOption(o)
s: String <- ZIO.fromFuture { implicit ec =>
bar(i)
}
} yield s
According to my IDE, t is of type ZIO[Any, Any, String] in this scenario. I don't know what to do with that.
I want to consider three possibilities:
The "success" case where foo produces a Some that can be composed with other functions on the value
The case where foo produces a None
The case where either function produces an error
I am not sure how to parse those possibilities in this scenario with ZIO. Any help is appreciated.
The type of ZIO.fromOption(o) is IO[Unit, A] which is ZIO[Any, Unit, A], whilst the type of ZIO.fromFuture is Task[A] which is ZIO[Any, Throwable, A], as documented by Type Aliases. Thus the types do not align
ZIO[Any, Unit, A]
ZIO[Any, Throwable, A]
Try mapError to align the error types to Throwable like so
for {
o <- ZIO.fromFuture { implicit ec => foo("test") }
i <- ZIO.fromOption(o).mapError(_ => new RuntimeException("boom"))
s <- ZIO.fromFuture { implicit ec => bar(i)}
} yield s
There are couple operators that can help you out in this case, basically instead of explicitly unwrapping the Option with fromOption, I would recommend using a combination of some and asSomeError
val t: Task[Option[String]] = (for {
// This moves the `None` into the error channel
i: Int <- ZIO.fromFuture(implicit ec => foo("test")).some
// This wraps the error in a Some() so that the signature matches
s: String <- ZIO.fromFuture(implicit ec => bar(i)).asSomeError
} yield s).optional // Unwraps the None back into the value channel

Scala - restrict generic type to anything other than Future

I have a couple of methods on a trait as so:
trait ResourceFactory[+R] {
def using[T](work: R => T): T
def usingAsync[T](work: R => Future[T]): Future[T]
}
Unfortunately there's nothing in the type checker to stop you calling the first using method with a function returning a Future. I'd like the compiler to insist that the type of T in the first method be anything other than Future, to prevent that mistake - is that possible?
Thanks
You can use shapeless' <:!<:
import scala.concurrent.Future
import shapeless._
trait ResourceFactory[+R] {
def using[T](work: R => T)(implicit ev: T <:!< Future[_]): T = ???
def usingAsync[T](work: R => Future[T]): Future[T] = ???
}
Then:
scala> val r = new ResourceFactory[Int] {}
r: ResourceFactory[Int] = $anon$1#effe6ad
// Compiles (the error is due to the use of ???)
scala> r.using(_.toString)
scala.NotImplementedError: an implementation is missing
// Doesn't compile
scala> r.using(Future.successful(_))
<console>:17: error: ambiguous implicit values:
both method nsubAmbig1 in package shapeless of type [A, B >: A]=> shapeless.<:!<[A,B]
and method nsubAmbig2 in package shapeless of type [A, B >: A]=> shapeless.<:!<[A,B]
match expected type shapeless.<:!<[scala.concurrent.Future[Int],scala.concurrent.Future[_]]
r.using(Future.successful(_))
Here's an alternative I've stolen shamelessly from https://github.com/japgolly/scalajs-react/blob/cb75721e3bbd0033ad63d380bcaddc96fbe906e3/core/src/main/scala/japgolly/scalajs/react/Callback.scala#L21-L31:
#implicitNotFound("You're returning a ${A}, which is asynchronous, which means the resource may be closed before you try and use it. Instead use usingAsync.")
final class NotFuture[A] private[ResourceFactoryTests]()
object NotFuture {
final class Proof[A] private[ResourceFactory]()
object Proof {
implicit def preventFuture1[A]: Proof[Future[A]] = ???
implicit def preventFuture2[A]: Proof[Future[A]] = ???
#inline implicit def allowAnythingElse[A]: Proof[A] = null
}
#inline implicit def apply[A: Proof]: NotFuture[A] = null
}
which can be used as:
trait ResourceFactory[+R] {
def using[T: ResourceGuard](work: R => T): T
def usingAsync[T](work: R => Future[T]): Future[T]
}
This has the advantage of not having to add the implicit arg every time you implement the method, but the disadvantage that I'm pure cargo culting - I don't understand why it works, though it seems to use similar principles to shapeless.

Switching between EitherT and Validation to accumulate error or traverse

Say I have the following function:
def getRemoteThingy(id: Id): EitherT[Future, NonEmptyList[Error], Thingy]
Given a List[Id], I can easily easily retrieve a List[Thingy] by using Traverse[List]:
val thingies: EitherT[Future, NonEmptyList[Error], List[Thingy]] =
ids.traverseU(getRemoteThingy)
It will use the Applicative instance for EitherT which will be based on flatMap so I will only get the first NonEmptyList[Error], it won't append all of them. Is that correct?
Now, if I actually want to accumulate errors, I can switch between EitherT and Validation. For example:
def thingies2: EitherT[Future, NonEmptyList[Error], List[Thingy]] =
EitherT(ids.traverseU(id => getRemoteThingy(id).validation).map(_.sequenceU.disjunction))
It works, I get all the errors at the end, but it is pretty cumbersome. I can make it simpler by using Applicative composition:
type ValidationNelError[A] = Validation[NonEmptyList[Error], A]
type FutureValidationNelError[A] = Future[ValidationNelError[A]]
implicit val App: Applicative[FutureValidationNelError] =
Applicative[Future].compose[ValidationNelError]
def thingies3: EitherT[Future, NonEmptyList[Error], List[Thingy]] =
EitherT(
ids.traverse[FutureValidationNelError, Thingy](id =>
getRemoteThingy(id).validation
).map(_.disjunction)
)
Longer than the others but all the plumbing can easily be shared across the code base.
What do you think of my solutions? Is there a more elegant way to solve this problem? How do you usually tackle it?
Thank you very much.
EDIT:
I have kind of a mad man solution using natural transformations to pimp Traversable. You apparently need type aliases for it to work, that's why I redefined getRemoteThingy:
type FutureEitherNelError[A] = EitherT[Future, NonEmptyList[String], A]
def getRemoteThingy2(id: Id): FutureEitherNelError[Thingy] = getRemoteThingy(id)
implicit val EitherTToValidation = new NaturalTransformation[FutureEitherNelError, FutureValidationNelError] {
def apply[A](eitherT: FutureEitherNelError[A]): FutureValidationNelError[A] = eitherT.validation
}
implicit val ValidationToEitherT = new NaturalTransformation[FutureValidationNelError, FutureEitherNelError] {
def apply[A](validation: FutureValidationNelError[A]): FutureEitherNelError[A] = EitherT(validation.map(_.disjunction))
}
implicit class RichTraverse[F[_], A](fa: F[A]) {
def traverseUsing[H[_]]: TraverseUsing[F, H, A] = TraverseUsing(fa)
}
case class TraverseUsing[F[_], H[_], A](fa: F[A]) {
def apply[G[_], B](f: A => G[B])(implicit GtoH: G ~> H, HtoG: H ~> G, A: Applicative[H], T: Traverse[F]): G[F[B]] =
HtoG(fa.traverse(a => GtoH(f(a))))
}
def thingies4: FutureEitherNelError[List[Thingy]] =
ids.traverseUsing[FutureValidationNelError](getRemoteThingy2)

Why Scala's Try has no type parameter for exception type?

I'm curious why scala.util.Try has no type parameter for the exception type like
abstract class Try[+E <: Throwable, +T] {
recoverWith[U >: T](f: PartialFunction[E, Try[E, U]]): Try[E, U]
...
}
Would help with documentation, e.g
def parseInt(s: String): Try[NumberFormatException, Int]
Still won't be able to express disjoint exception types like throws SecurityException, IllegalArgumentException, but at least one step in this direction.
This might be what you're looking for:
import scala.util.control.Exception._
import scala.util.{ Success, Failure }
def foo(x: Int): Int = x match {
case 0 => 3
case 1 => throw new NumberFormatException
case _ => throw new NullPointerException
}
val Success(3) = catching(classOf[NumberFormatException]).withTry(foo(0))
val Failure(_: NumberFormatException) = catching(classOf[NumberFormatException]).withTry(foo(1))
// val neverReturns = catching(classOf[NumberFormatException]).withTry(foo(2))
See scala.util.control.Exception$
However, there's no way to specialize Try[T] to something like the hypothetical Try[ExcType, T]; in order for that to work you'll need something like Either (but possibly something more sophisticated such as scalaz.\/, or, for more than 1 exception class, Shapeless' Coproduct):
def bar(x: Int): Either[NumberFormatException, Int] = {
catching(classOf[NumberFormatException]).withTry(foo(x)) match {
case Success(x) => Right(x)
case Failure(exc) => Left(exc.asInstanceOf[NumberFormatException])
}
}
println(bar(0)) // Right(3)
println(bar(1)) // Left(java.lang.NumberFormatException)
// println(bar(2)) // throws NullPointerException
It should be possible to generalize that into a generic helper that works with any number of exception types. You'd definitely have to work with Shapeless' Coproduct and facilities for abstracting over arity in that case. Unfortunately, it's a non-trivial exercise and I don't have the time to implement that for you right now.