Reputation: 19854
My code consists of a number of scala.concurrent.Futures returned from invoking Akka actors
So sample code in my actor would be something like the following:
val ivResult: Future[Any] = ask(ivActor, ivId)
// perform mapping is a function of type (Any) => Unit
val ivMapping: Future[Unit] = ivResult.map(performingMapping)
// erLookup is a function of type (Any) => Future[Any]
val erResult: Future[Any] = ivResult.flatMap(erLookup)
And so on. Code basically consists of future.flatmap().map() to perform aggregation logic
My problem is that I want to implement null-safe logic such that if the result of a future is null then we don't throw NullPointerExceptions
Obviously, I can embed null-safe checks in each of my functions, but these seems a little verbose considering Scala's powerful capabilities.
Therefore, I'm looking to find out if there is a more elegant way to do this, perhaps using implicits etc.
Upvotes: 0
Views: 959
Reputation: 39577
The result of a Future
already indicates whether it completed successfully.
Normally, on NPE your code would blow up and the future is failed.
You're saying, Please don't let my code blow up, with possibly deleterious side effects.
So you want a guard with a special failure state.
Something like:
scala> object DeadFuture extends Exception with NoStackTrace
defined object DeadFuture
scala> implicit class SafeFuture[A](val f: Future[A]) {
| def safeMap[B](m: A => B)(implicit x: ExecutionContext) =
| f.map { a: A => if (a == null) throw DeadFuture else m(a) }(x)
| def safeFlatMap[B](m: A => Future[B])(implicit x: ExecutionContext) =
| f.flatMap { a: A => if (a == null) (Future failed DeadFuture) else m(a) }(x)
| }
then
scala> case class Datum(i: Int)
defined class Datum
scala> def f(d: Datum): Int = 2 * d.i
f: (d: Datum)Int
scala> def g(d: Datum): Future[Int] = Future(2 * d.i)
g: (d: Datum)scala.concurrent.Future[Int]
scala> Future[Datum](null) safeMap f
res1: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@5fa5debb
scala> .value
res3: Option[scala.util.Try[Int]] = Some(Failure(DeadFuture$))
scala> def g(d: Datum): Future[Int] = Future(2 * d.i)
g: (d: Datum)scala.concurrent.Future[Int]
scala> Future[Datum](null) safeFlatMap g
res4: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@7f188fd
scala> .value
res5: Option[scala.util.Try[Int]] = Some(Failure(DeadFuture$))
There might be a better way to induce the conversion, so you keep for-comprehensions.
for (i <- safely(futureInt)) f(i)
Upvotes: 1