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
Related
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'm reading the docs on implicits in Scala, and there is an example of a function with implicit conversion as parameter:
def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)
I understand how it works, but I don't understand what's the point of writing it like that instead of:
def getIndexExplicit[T](seq: Seq[T], value: T) = seq.indexOf(value)
As far as I know, if the conversion from the argument seq to type Seq[T] exists, the compiler would still allow the call to getIndexExplicit?
To illustrate my point, I prepared this simple example:
def equal42[T](a: T)(implicit conv: T => Int) = conv(a) == 42 // implicit parameter version
def equal42Explicit(a: Int) = a == 42 // just use the type in the signature
implicit def strToInt(str: String): Int = java.lang.Integer.parseInt(str) // define the implicit conversion from String to Int
And indeed, both functions seem to work in the same way:
scala> equal42("42")
res12: Boolean = true
scala> equal42Explicit("42")
res13: Boolean = true
If there is no difference, what's the point of explicitly defining the implicit conversion?
My guess is that in this simple case it makes no difference, but there must be some more complex scenarios where it does. What are those?
In your super-simple example:
equal42("42")
equal42Explicit("42")
is equal to
equal42("42")(strToInt)
equal42Explicit(strToInt("42"))
which in case of your definition make no difference.
BUT if it did something else e.g.
def parseCombined[S, T](s1: S, s2: S)
(combine: (S, S) => S)
(implicit parse: S => Option[T]): Option[T] =
parse(combine(s1, s2))
then when you would apply conversion matters:
implicit def lift[T]: T => Option[T] = t => Option(t)
implicit val parseInt: String => Option[Int] = s => scala.util.Try(s.toInt).toOption
implicit def parseToString[T]: T => Option[String] = t => Option(t.toString)
parseCombined[Option[Int], String]("1", "2") { (a, b) => a.zip(b).map { case (x, y) => x + y } } // Some("Some(3)")
parseCombined[String, Int]("1", "2") { _ + _ } // Some(12)
In the first case arguments were converted before passing (and then again inside), while in the other case they were converted only inside a function at a specific place.
While this case is somewhat stretched, it shows that having control over when conversion is made might matter to the final result.
That being said such usage of implicit conversions is an antipattern and type classes would work much better for it. Actually extension methods are the only non-controversial usages of implicit conversions because even magnet pattern - the only other use case that might be used on production (see Akka) - might be seen as issue.
So treat this example from docs as a demonstration of a mechanism and not as a example of good practice that should be used on production.
I wrote my own Either-like monad class called Maybe with either a value or an error object inside it. I want objects of this class to combine with Future, so that I can turn a Maybe[Future[T], E]] into a Future[Maybe[T, E]]. Therefore I implemented two flatMap methods:
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
sealed abstract class Maybe[+E, +V] {
def map[W](f: V ⇒ W ): Maybe[E, W] = this match {
case Value(v) ⇒ Value(f(v))
case Error(_) ⇒ this.asInstanceOf[Error[E, W]]
}
def flatMap[F >: E, W](f: V ⇒ Maybe[F, W]): Maybe[F, W] = this match {
case Value(v) ⇒ f(v)
case Error(_) ⇒ this.asInstanceOf[Error[F, W]]
}
def flatMap[W](f: V ⇒ Future[W]): Future[Maybe[E, W]] = this match {
case Value(v) ⇒ f(v).map(Value(_))
case Error(_) ⇒ Future.successful(this.asInstanceOf[Error[E, W]])
}
}
final case class Value[+E, +V](value: V) extends Maybe[E, V]
final case class Error[+E, +V](error: E) extends Maybe[E, V]
However, when I use the for comprehension to combine a Maybe and a Future which holds another Maybe the Scala compiler gives me the error message missing parameter type at the line of the outer generator:
def retrieveStreet(id: String): Future[Maybe[String, String]] = ...
val outerMaybe: Maybe[String, String] = ...
val result = for {
id ← outerMaybe // error message "missing parameter type" here!
street ← retrieveStreet(id)
} yield street
But when instead of using for I call the flatMapand mapmethods explicitly, it works:
val result2 =
outerMaybe.flatMap( id => retrieveStreet(id) )
.map( street => street )
(I also get this error message when I try to combine a Maybe with another Maybe in a for comprehension.)
So the questions are:
Shouldn't these two alternatives behave exactly the same? Why does the compiler figure out the correct flatMap method to call, when calling flatMap explicitly?
Since apparently the compiler is confused by the two flatMap implementations, is there a way to tell it (by a type specification anywhere) which one should be called in the for comprehension?
I am using Scala 2.11.8 in Eclipse.
I can't give you a comprehensive answer, but running it through scalac -Xprint:parser, I can tell you that the 2 alternatives actually desugar slightly differently, there's a good chance this is the source of your issue.
val result1 = outerMaybe
.flatMap(((id) => retrieveStreet(id)
.map(((street) => street))));
val result2 = outerMaybe
.flatMap(((id) => retrieveStreet(id)))
.map(((street) => street))
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.
I try to define the Reader monad with scalaz like this:
import scalaz._
import Scalaz._
final class Reader[E,A](private[Reader] val runReader: E => A)
object Reader {
def apply[E,A](f: E => A) = new Reader[E,A](f)
def env[E]: Reader[E,E] = Reader(identity _)
implicit def ReaderMonad[E] = new Monad[PartialApply1Of2[Reader,E]#Apply] {
def pure[A](a: => A) = Reader(_ => a)
def bind[A,B](m: Reader[E,A], k: A => Reader[E,B]) =
Reader(e => k(m.runReader(e)).runReader(e))
}
}
object Test {
import Reader._
class Env(val s: String)
def post(s: String): Reader[Env, Option[String]] =
env >>= (e => if (e.s == s) some(s).pure else none.pure)
}
but I get a compiler error:
reader.scala:27: reassignment to val
env >>= (e => if (e.s == s) some(s).pure else none.pure)
^
Why is that?
Thanks,
Levi
This error is fairly opaque, even by Scala's standards. Method names ending with = are treated specially -- they are first considered as a normal identifier, and failing that, they are expanded to a self assignment.
scala> def env[A] = 0
env: [A]Int
scala> env >>= 0
<console>:7: error: reassignment to val
env >>= 0
^
scala> env = env >> 0
<console>:6: error: reassignment to val
env = env >> 0
^
If you're confused about the syntactic interpretation of your program, it's a good idea to run scalac -Xprint:parser to see what's going on. Similarly, you can use -Xprint:typer or -Xprint:jvm to see later phases of the program transformation.
So, how do you call >>= on your Reader? First of all, you'll need to explicitly pass the type argument Env to env. The resulting Reader[Env, Env] must then be converted to a MA[M[_], A]. For simple type constructors, the implicit conversion MAs#ma will suffice. However the two param type constructor Reader must be partially applied -- this means it can't be inferred and instead you must provide a specific implicit conversion.
The situation would be vastly improved if Adriaan ever finds a spare afternoon to implement higher-order unification for type constructor inference. :)
Until then, here's your code. A few more comments are inline.
import scalaz._
import Scalaz._
final class Reader[E, A](private[Reader] val runReader: E => A)
object Reader {
def apply[E, A](f: E => A) = new Reader[E, A](f)
def env[E]: Reader[E, E] = Reader(identity _)
implicit def ReaderMonad[E]: Monad[PartialApply1Of2[Reader, E]#Apply] = new Monad[PartialApply1Of2[Reader, E]#Apply] {
def pure[A](a: => A) = Reader(_ => a)
def bind[A, B](m: Reader[E, A], k: A => Reader[E, B]) =
Reader(e => k(m.runReader(e)).runReader(e))
}
// No Higher Order Unification in Scala, so we need partially applied type constructors cannot be inferred.
// That's the main reason for defining function in Scalaz on MA, we can create one implicit conversion
// to extract the partially applied type constructor in the type parameter `M` of `MA[M[_], A]`.
//
// I'm in the habit of explicitly annotating the return types of implicit defs, it's not strictly necessary
// but there are a few corner cases it pays to avoid.
implicit def ReaderMA[E, A](r: Reader[E, A]): MA[PartialApply1Of2[Reader, E]#Apply, A] = ma[PartialApply1Of2[Reader, E]#Apply, A](r)
}
object Test {
import Reader._
class Env(val s: String)
def post(s: String): Reader[Env, Option[String]] =
// Need to pass the type arg `Env` explicitly here.
env[Env] >>= {e =>
// Intermediate value and type annotation not needed, just here for clarity.
val o: Option[String] = (e.s === s).guard[Option](s)
// Again, the partially applied type constructor can't be inferred, so we have to explicitly pass it.
o.pure[PartialApply1Of2[Reader, Env]#Apply]
}
}