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?
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
                        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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With