I have a simple method which returns Future[Either[Throwable, MyValue]].
Now I would like to convert it to ZIO. I created:
def zioMethod(): ZIO[Any, Throwable, MyValue] =
ZIO.fromFuture {implicit ec =>
futureEitherMethod().flatMap { result =>
Future(result)
}
}
But it didn't work without trace: No implicit found for parameter trace: Trace.
So, after I added Trace as implicit it still doesn't work correctly.
Is any other way to convert Future[Either[,]] into ZIO? Docs are not clear in this topic.
The signature (in the code or in the scaladoc) seems to be rather clear on this topic:
ZIO.fromFuture[A] converts a Future[A] into a ZIO[Any, Throwable, A].
You pass in a Future[Either[Throwable, MyValue]], so it would return a ZIO[Any, Throwable, Either[Throwable, MyValue]] - not a ZIO[Any, Throwable, MyValue] as your code expects.
To transfer the Left case of your Either onto the "error channel" of the ZIO, you have several options, e.g.:
Convert Future[Either[Throwable, MyValue]] to Future[MyValue] and then to ZIO:
def zioMethod(): ZIO[Any, Throwable, MyValue] =
ZIO.fromFuture { implicit ec =>
futureEitherMethod().flatMap {
case Right(myValue) => Future.successful(myValue)
case Left(throwable) => Future.failed(throwable)
}
}
Convert to ZIO[Any, Throwable, Either[Throwable, MyValue]] and then to ZIO[Any, Throwable, MyValue]:
def zioMethod(): ZIO[Any, Throwable, MyValue] = {
val fromFuture: ZIO[Any, Throwable, Either[Throwable, MyValue]] =
ZIO.fromFuture { implicit ec =>
futureEitherMethod()
}
fromFuture.flatMap(ZIO.fromEither)
}
BTW: for me, this compiles without bringing any additional implicits in code: https://scastie.scala-lang.org/dRefNpH0SEO5aIjegV8RKw
Update: alternative (more idiomatic?) solution
As pointed out in the comments by #LMeyer, there's also ZIO.absolve and to me, that seems to be the more ZIO-idiomatic way (I have close to zero experience with ZIO myself):
ZIO.fromFuture { implicit ec =>
futureEitherMethod()
}.absolve
Related
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.
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.
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
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.
I try to implement some simple conversion to/from "JsValue" ( from play framework library) using "shapeless", as the beginning of my acquaintance with it. I know that such a thing is realized in a spray-json-shapeless library to work with sparay-json.
As I can see encoding goes well, but I got a problem with decoding.
This is my test:
"json de/serialization for N fields case class" should {
"work as expected" in {
val nFields = NFields(1, 2.3, "test", true, 3)
val jsValue = JsonEncoder[NFields].encode(nFields)
val result = JsonDecoder[NFields].decode(jsValue)
println(result.toString)
result must equal(nFields)
}
}
After encoding object to JsValue I get the correct JsObject like this: {JsObject#1686}{"f1":1,"f2":2.3,"f3":"test","f4":true,"f5":3} but after decoding, the result object is NFields(1,3.0,test,true,2) so the value of field “f2: Double” “2.3” falls into place of field “f5: Int” as “2” and the value of field “f5: Int” “3” falls into place of field “f2: Double” as “3.0” and of course test fails with “NFields(1,3.0,test,true,2) did not equal NFields(1,2.3,test,true,3)”.
My Hlist decoder:
implicit def hlistDecoder[K <: Symbol, H, T <: HList](
implicit
key: Witness.Aux[K],
hDecoder: Lazy[JsonDecoder[H]],
tDecoder: JsonDecoder[T]): JsonDecoder[FieldType[K,H] :: T] =
createInstance{
case obj: JsObject =>
val value= obj.value
field[K](hDecoder.value.decode(value.head._2)) :: tDecoder.decode(JsObject(obj.value.tail))
}
and Generic decoder:
implicit def genericDecoder[A, R](
implicit
gen: LabelledGeneric.Aux[A, R],
dec: Lazy[JsonDecoder[R]]
): JsonDecoder[A] = createInstance(a => gen.from(dec.value.decode(a)))
Where is the problem? Any hints appreciated! Thanks in advance.