I have the following very ugly nesting of if-statements that involve slow operations (tryFindResult
and tryFindOtherResult
) that I need to execute as little as possible. Just to give some context, it's part of a parser that looks at the current line (line
) at some position (i
). Each case in the if-statement produces side-effects (increases i
).
if (cond) {
// increase i
} else {
val maybeResult: Option[Result] = tryFindResult(line, i)
if (maybeResult.nonEmpty) {
// depending on `maybeResult`, increase i
} else {
val (newOffset, maybeOtherResult): (Int, Option[Result2]) = tryFindOtherResult(line, i)
if (maybeOtherResult.nonEmpty) {
// depending on `maybeOtherResult`, increase i
} else {
// i = newOffset
}
}
}
Is there a way I can flatten this? As far as I know, Scala has no inline assignment syntax like if (val o = tryFindOtherResult && o.nonEmpty)
, right?
Note that moving tryFindResult
and tryFindOtherResult
to the beginning of the program would solve everything, but these are long-running operations that I can't execute unconditionally (the program is part of a parser and needs to jump forward in the last if/else-branch to avoid quadratic complexity).
Are there tricks with match statements, or map
/flatMap
/getOrElse
on Options that I can use to avoid this kind of nesting?
You can use the methods defined on the Option
type and its companion object. I'm not entirely sure of the semantics of each case, but it would roughly look as follows:
final case class Result(i: Int)
final case class Result2(i: Int)
def tryFindResult(line: Int, i: Int): Option[Result] =
if (line == 42) Some(Result(42)) else None
def tryFindOtherResult(line: Int, i: Int): (Int, Option[Result2]) =
if (line == 47) (47, Some(Result2(47))) else (-1, None)
def method(cond: Boolean, line: Int, i: Int, fallback: Int): Int =
Option
.when(cond)(i + 1)
.orElse(tryFindResult(line, i).map(_.i + 1))
.orElse {
val (_, result) = tryFindOtherResult(line, i)
result.map(_.i + 1)
}
.getOrElse(fallback)
assert(method(cond = true, line = 42, i = 0, fallback = 100) == 1)
assert(method(cond = true, line = 42, i = 100, fallback = 100) == 101)
assert(method(cond = false, line = 42, i = 0, fallback = 100) == 43)
assert(method(cond = false, line = 47, i = 0, fallback = 100) == 48)
assert(method(cond = false, line = 33, i = 0, fallback = 100) == 100
Notice that the fallback passed to orElse
and the function passed to when
are both by-name parameters (see the documentation here), which makes them evaluated lazily, i.e. only if they need to be used.
You can play around with this code here on Scastie.
You can read more about orElse
here and about when
here.
It looks like you actually need to mutate a variable. Consider the option of keeping most of your code as is, if you realize that the usage of the methods I suggested obscures the side effects. Sometimes a nested if
might be precisely what you need.