Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: better nested multiple condition check

Lately, I frequently end up writing code like that:

def doSomethingWithLotsOfConditions(arg1, arg2, arg3...) {
  arg1.get(arg2) match {
    case Some(value1) =>
      arg3.get(value1) match {
        case Some(value2) =>
          arg4.get(arg5, value2) match {
            case Some(value3) =>
              finallyDoSomethingInside(value3)
            case None =>
              log("Some excuse for being unable to work with arg4/arg5...")
          }
        case None =>
          log("Some excuse for being unable to work with arg3")
      }
    case None =>
      log("Some excuse for being unable to work with arg1/arg2")
  }
}

A somewhat related question seems to heavily advocate for such usage of nested match, although, from my point of view, it hardly seems readable, concise or easy-to-understand: (1) it kind of splits the check itself and its aftermath, (2) it makes code uncontrollably nested without any real rationale for nesting. In these particular cases, I would be glad to structure the code something in lines of:

def doSomethingWithLotsOfConditions(arg1, arg2, arg3...) {
  // Step 1
  val value1Opt = arg1.get(arg2)
  if (value1Opt.isEmpty) {
    log("Some excuse for being unable to work with arg1/arg2")
    return
  }
  val value1 = value1Opt.get

  // Step 2
  val value2Opt = arg3.get(value1)
  if (value2Opt.isEmpty) {
    log("Some excuse for being unable to work with arg3")
    return
  }
  val value2 = value2Opt.get

  // Step 3
  val value3Opt = arg4.get(arg5, value2)
  if (value3Opt.isEmpty) {
    log("Some excuse for being unable to work with arg4/arg5...")
    return
  }
  val value3 = value3Opt.get

  // All checked - we're free to act!
  finallyDoSomethingInside(value3)
}

However, that pattern (i.e. valueXOpt = (...).get => check isEmpty => value = valueXOpt.get) looks really ugly and is also definitely too verbose. Hell, even Java version would look more concise:

Value1Type value1 = arg1.get(arg2);
if (value1 != null) {
    log("Some excuse for being unable to work with arg1/arg2");
    return;
}

Is there a better, cleaner alternative, i.e. for getting the value and specifying alternative short escape route (that log a line + return), without going nested with matches?

like image 599
GreyCat Avatar asked Sep 08 '14 04:09

GreyCat


3 Answers

How about this?

object Options{
  implicit class OptionLog[T](val option:Option[T]) extends AnyVal{

    def ifNone(body: =>Unit):Option[T] = option.orElse {
       body
       option
    }
   }
}

import Options._

def something(arg1:Option[Int], arg2:Option[String], arg3:Option[Long], arg4:Option[Any]){
  for{
    val1 <- arg1 ifNone(println("arg1 was none"))
    val2 <- arg2 ifNone(println("arg2 was none"))
    val3 <- arg3 ifNone(println("arg3 was none"))
  }{
    println(s"doing something with $val1, $val2, $val3")
  }
}

Then ...

scala> something(Some(3), Some("hello"), None, Some("blah"))
arg3 was none

scala> something(Some(3), Some("hello"), Some(10l), Some("blah"))
doing something with 3, hello, 10
like image 61
Julio Avatar answered Nov 18 '22 10:11

Julio


Maybe you mean, for a condition x:

scala> def f(x: Option[Int]): Int = x orElse { println("nope"); return -1 } map (_ + 1) getOrElse -2
f: (x: Option[Int])Int

scala> f(Some(5))
res3: Int = 6

scala> f(None)
nope
res4: Int = -1

or even

scala> def f(x: Option[Int], y: Option[Int]): Int = (for (i <- x orElse { println("nope"); return -1 }; j <- y orElse { println("gah!"); return -2 }) yield i + j) getOrElse -3
f: (x: Option[Int], y: Option[Int])Int

scala> f(Some(5), None)
gah!
res5: Int = -2

Sorry if I'm oversimplifying.

like image 31
som-snytt Avatar answered Nov 18 '22 12:11

som-snytt


Don't you want to use the map method?

def doSomethingWithLotsOfConditions(arg1, arg2, arg3...) =
  arg1.get(arg2).map(value1 =>
    arg3.get(value1).map(value2 =>
      arg4.get(arg5, value2).map(value3 => 
        finallyDoSomethingInside(value3)).
      getOrElse(log("Some excuse for being unable to work with arg4/arg5"))).
    getOrElse(log("Some excuse for being unable to work with arg3"))).
  getOrElse(log("Some excuse for being unable to work with arg1/arg2"))

It is still nested but it's at least more elegant-looking than your pattern matching above.

Or you can implement your own Functor for this one:

trait Foo[+A] {
  def fmap[B](f: A => B): Foo[B]
}

case class Bar[A](value: A) {
  def fmap[B](f: A => B): Foo[B] = Bar(f(value))
}

case object Error[Nothing](message: String) {
  def fmap[B](f: Nothing => B) = Error(message)
}

def doSomethingWithLotsOfConditions(arg1, arg2, arg3, arg4, arg5) = 
  arg1.get(arg2).fmap(value1 => 
    arg3.get(value1).fmap(value2 => 
      arg4.get(arg5, value2).fmap(value3 => 
        finallyDoSomethingInside))

def processResult = doSomethingWithLotsOfConditions(...) match {
  case Bar(a) => doSomething
  case Error(message) => log(message)
}

This code assumes that arg.get returns a Foo.

like image 1
Melvic Ybanez Avatar answered Nov 18 '22 12:11

Melvic Ybanez