DJ180
DJ180

Reputation: 19854

Implementing null safe logic in Scala Futures map and flatmap

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

Answers (1)

som-snytt
som-snytt

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

Related Questions