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