Home > Enterprise >  Best way to write this conditional statements in scala
Best way to write this conditional statements in scala

Time:03-10

I have two objects of same type but I want to call methods using the objects but depending upon couple of boolean values.

def myMethod(a: Boolean, b: Boolean, obj1: MyClass, obj2: MyClass): Future[Done] = {
    if(a) obj1.methodWhichReturnsFuture()
    if(b) obj2.methodWhichReturnsFuture()
}

Here the method #methodWhichReturnsFuture has return typr Future[Done]. THis is showing error since I am returning Unit here. I though of creating a 'var' and assigning it inside both method call but looks like its not a good way. This is definitely basic but I want to know what is the better way to implement this? Thanks

CodePudding user response:

In Scala, an if without an else can only be executed for side-effect, so the resulting value of

if (a) obj1.methodWhichReturnsFuture()

will be discarded and the singleton value for Unit (()) will be the result. The same will happen in the if (b) statement (and technically that () will be the result of myMethod, but it's the same () as from the if (a)).

You will thus need to add else clauses which also result in a Future[Done] and determine what you want the result to be if both a and b are true (furthermore, if you want both the obj1 and obj2 calls to be made, you will need to decide whether to execute them sequentially or in parallel) as well as if both a and b are false.

Thankfully, because Done is a singleton (it's basically the same as Unit as far as encoding "this happened", but in a way which is nicer for Java code using Akka), there's a reasonable default Future[Done]: Future.successful(Done) (which can basically be interpreted as "yeah, sure, I did it (but there was nothing to actually do)").

So if you want to do both the obj1 and obj2 calls simultaneously (or at least without a defined "happens-before" relationship), you could write:

val obj1Fut = if (a) obj1.methodWhichReturnsFuture() else Future.successful(Done)
val obj2Fut = if (b) obj2.methodWhichReturnsFuture() else Future.successful(Done)

// exploits eagerness of Future, won't complete until both obj1Fut and obj2Fut have completed
obj1Fut.flatMap(_ => obj2Fut)

If you don't want to perform the obj2 call until after the obj1 call:

val obj1Fut = if (a) obj1.methodWhichReturnsFuture() else Future.successful(Done)

obj1Fut.flatMap { _ =>
  if (b) obj2.methodWhichReturnsFuture()
  else Future.successful(Done)
}

Note that in the Unit returning code you presented, it's quite possible for the obj2 future to complete before obj1's future has completed.

And if you only wanted to do the obj1 call if both a and b were true:

if (a) obj1.methodWhichReturnsFuture()
else if (b) obj2.methodWhichReturnsFuture()
else Future.successful(Done)

// or the equivalent pattern match, which might be more legible than if else if else
// (a, b) match {
//   case (true, _) => obj1.methodWhichReturnsFuture()
//   case (false, true) => obj2.methodWhichReturnsFuture()
//   case (false, false) => Future.successful(Done)
// }
  • Related