Trivial library for correct and stable conversions between Task
and Future
. To use:
For scalaz-7.1.x
:
libraryDependencies += "io.verizon.delorean" %% "core" % "1.2.40-scalaz-7.1"
For scalaz-7.2.x
:
libraryDependencies += "io.verizon.delorean" %% "core" % "1.2.40-scalaz-7.2"
scalaz-7.0.x
is not supported.
Cross builds are available for 2.10.x, 2.11.x and for 2.12.x
Keep it simple:
import delorean._ // conversions are in the package object
val f: Future[Foo] = ...
val t: Task[Foo] = f.toTask
val f2: Future[Foo] = t.unsafeToFuture
As a general rule of thumb:
def toTask[A](f: Future[A]) = Task { Await.result(f, Duration.Inf) }
def unsafeToFuture[A](t: Task[A]) = Future { t.run }
Obviously, the above is misleading since the definitions of these functions are non-blocking, but this should give you an idea of their rough semantics.
There are a few subtle things that are hidden here by the pretty syntax.
toTask
is lazy in theFuture
parameter, meaning that evaluation of the method receiver will be deferred. This is significant asFuture
is eagerly evaluated and caching. It is ideal to ensure that you do not accidentally eagerly evaluateFuture
values before they are fed intotoTask
(note: we break this rule in the above example, sincef
is aval
)- The
Task
resulting fromtoTask
will have sane semantics (i.e. it will behave like a normalTask
) with respect to reevaluation and laziness, provided that you are disciplined and ensure that theFuture
value that is the dispatch receiver is not eagerly evaluated elsewhere (e.g. in aval
). This means that you can reason about the results oftoTask
in a referentially transparent fashion, as if the constituentFuture
had been constructed as aTask
all along. toTask
takes an implicitExecutionContext
and an implicitStrategy
. You should make sure that the appropriate values of each are in scope. If you are not overriding Scalaz's default implicitStrategy
for the rest of yourTask
composition, then you can just rely on the defaultStrategy
. The point is just to make sure that theTask
resulting fromtoTask
is context-shifted into the same thread pool you're using for "normal"Task
(s).unsafeToFuture
is exactly as unsafe as it sounds, since theFuture
you get back will be running eagerly. Do not make use of this function unless you are absolutely sure of what you're doing! It is very dangerous, because it defeats the expected laziness of yourTask
computation. This function is meant primarily for interop with legacy libraries that require values of typescala.concurrent.Future
.toTask
andunsafeToFuture
are not strict inverses. They add overhead and defeat fairness algorithms in both scala.concurrent and scalaz/scalaz-stream. So… uh… don't repeatedly convert back and forth, k?
There are two major pitfalls here. First, the toTask
conversion can only give you a referentially transparent Task
if you religiously avoid eagerly caching its dispatch receiver. As a general rule, this isn't a bad idiom:
def f: Future[A] = ???
val t: Task[A] = f.toTask
In other words, f
is a def
and not a val
. With this sort of machinery, toTask
will give you a reasonable output. If you eagerly cache its input Future
though, the results are on your own head.
Second, unsafeToFuture
immediately runs the input Task
(as mentioned above). There really isn't anything else that could be done here, since Future
is eager, but it's worth mentioning. Additionally, unsafeToFuture
makes no attempt to thread-shift the input Task
, since in general this is not possible (or necessarily desirable). The resulting Future
will be on the appropriate thread pool, and it is certainly safe to flatMap
on said Future
and treat it normally, but the computation itself will be run on whatever thread scheduler the Task
was composed against.