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.unsafeToFutureAs 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.
toTaskis lazy in theFutureparameter, meaning that evaluation of the method receiver will be deferred. This is significant asFutureis eagerly evaluated and caching. It is ideal to ensure that you do not accidentally eagerly evaluateFuturevalues before they are fed intotoTask(note: we break this rule in the above example, sincefis aval)- The
Taskresulting fromtoTaskwill 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 theFuturevalue that is the dispatch receiver is not eagerly evaluated elsewhere (e.g. in aval). This means that you can reason about the results oftoTaskin a referentially transparent fashion, as if the constituentFuturehad been constructed as aTaskall along. toTasktakes an implicitExecutionContextand an implicitStrategy. You should make sure that the appropriate values of each are in scope. If you are not overriding Scalaz's default implicitStrategyfor the rest of yourTaskcomposition, then you can just rely on the defaultStrategy. The point is just to make sure that theTaskresulting fromtoTaskis context-shifted into the same thread pool you're using for "normal"Task(s).unsafeToFutureis exactly as unsafe as it sounds, since theFutureyou 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 yourTaskcomputation. This function is meant primarily for interop with legacy libraries that require values of typescala.concurrent.Future.toTaskandunsafeToFutureare 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.toTaskIn 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.