Reputation: 1462
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
?
Upvotes: 3
Views: 1249
Reputation: 55690
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
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...
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!
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)
}
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.
Upvotes: 3
Reputation: 4491
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)
Upvotes: 2