This is the sixth of several posts describing the evolution of scala.concurrent.Future in Scala 2.12.x.
For the previous post, click here.
Missing utilities: unit & never
Something I’ve always felt was missing is having a «zero» for Future—or more frequently called a «default instance» of a Future.
What’s nice about having this is that, technically, Future.apply[T](logic: => T)(implicit ec: ExecutionContext) could be viewed as, and implemented like:
def apply[T](logic: => T)(implicit ec: ExecutionContext): Future[T] =
unit.map(_ => logic)
Q: Where is it useful?
A: Anywhere you have Future.successful(())
Example:
//Imagine we don't want to try to store nulls or empty strings
//Returns a Future[Unit] which will be completed once the operation has completed
def storeInDB(s: String): Future[Unit] = s match {
case null | "" => Future.unit
case other => db.store(s)
}
val f: Future[String] = …
val f2 = f flatMap storeInDB
Another important scenario, which wasn’t really readily solvable unless you were comfortable with implementing it yourself was a Future which would never complete.
Now, the naïve implemention of said Future would look something like:
val neverCompletingFuture: Future[Nothing] = Promise[Nothing].future
Take a few seconds to think about the following question: In what ways would that solution be undesirable?
…
…
…
…
Hint: What happens with logic which gets added to neverCompletingFuture?
Example:
val fooFuture = neverCompletingFuture.map(_ => "foo")
//or
neverCompletingFuture.onComplete(println)
Well, what happens is that the logic is packaged and added to the neverCompletingFuture instance in order to be executed when neverCompletingFuture completes (which is never), and now we have a hard to spot invisible memory leak!
So, in order to support the case when you want to be able to represent a Future which never completes, but also doesn’t leak memory when used as a plain Future, use Future.never.
An example use-case might be when you need to pass in a Future which should not affect the outcome, as in:
val someImportantFuture: Future[String] = …
val someLessImportantFuture: Future[String] =
if (someCondition) Future.never else Future.successful("pigdog")
// Will always pick someImportantFuture if someCondition is true
val first = Future.firstCompletedOf(someImportantFuture, someLessImportantFuture)
Benefits:
- Future.unit is a «zero» instance which is cached
- Future.never removes the risk of memory leaks when used as a
Futureinstance which never completes
Click here for the next part in this blog series.
Cheers,
√
