David Moles
David Moles

Reputation: 51199

Idiomatic Scala for Options in place of if/else/else chain

I often find myself writing Scala of the form:

def foo = {
  f1() match {
    case Some(x1) => x1
    case _ =>
      f2() match {
        case Some(x2) => x2
        case _ =>
          f3() match {
            case Some(x3) => x3
            case _ =>
              f4()
          }
      }
  }
}

This is the moral equivalent of Java's

Object foo() {
    Object result = f1();
    if (result != null) {
        return result;
    } else {
        result = f2();
        if (result != null) {
            return result;
        } else {
            result = f3();
            if (result != null) {
                return result;
            } else {
                return f4();
            }
        }
    }
}

and it seems ugly and unnecessarily verbose. I feel like there should be a readable way to do this in one line of Scala, but it's not clear to me what it is.

Note: I looked at Idiomatic Scala for Nested Options but it's a somewhat different case.

Upvotes: 5

Views: 6750

Answers (4)

netchkin
netchkin

Reputation: 1384

I know I am way late to the party, but feel that the orElse solution here is a bit clumsy. For me, the general functional idiom (not just scalaic) would be sth. along these lines (forgive me, I am not scala proficient):

def f1 = () => { println("f1 here"); null }
def f2 = () => { println("f2 here"); null }
def f3 = () => { println("f3 here"); 2 }
def f4 = () => { println("f4 here"); 3 }
def f5 = () => { println("f5 here"); 43 }

Stream(f1, f2, f3, f4, f5)
  .map(f => f())
  .dropWhile(_ == null)
  .head

You use Stream as a lazy list, and basically, you say: Start invoking the functions and give me the first that does not evaluate to zero. Combining declarative approach and laziness gives you this generic piece of code, where the only thing you need to change when number of functions change, is the input list (stream) by adding just one more function element. That way, functions f1...fn become data, so you do not have to modify any existing code.

Upvotes: 8

Regis Blanc
Regis Blanc

Reputation: 549

The idiomatic way to write nested pattern matching with options in Scala is by using the methods map, flatMap, orElse, and getOrElse.

You use map when you want to process the content of the option further and keep the optional behaviour:

So instead of this:

val opt: Option[Int] = ???
opt match {
  case Some(x) => Some(x + 1)
  case None => None
}

You would do this:

val opt: Option[Int] = ???
opt.map(_ + 1)

This chains much more naturally:

opt.map(_ + 1).map(_ * 3).map(_ - 2)

flatMap is verlly similar, but is used when your further operations return an option type as well.

In your particular example, orElse seems to be the most adapted solution. You can use orElse to return the option itself if not empty or else return the argument. Note that the argument is lazily evaluated so it is really equivalent to nested pattern matching/if-then-else.

def foo = {
  f1().orElse(f2())
      .orElse(f3())
      .orElse(f4())
}

You can also combine these with getOrElse if you want to get rid of an Option. In your example you seem to return the final f4 as if it did not return an Option, so you would do the following:

def foo = {
  f1().orElse(f2())
      .orElse(f3())
      .getOrElse(f4())
}

Upvotes: 10

Nexus6
Nexus6

Reputation: 1486

A slight tweak on cmbaxter's response that saves a few characters but is otherwise the same.

def foo = f1 orElse f2 orElse f3 getOrElse f4

Upvotes: 0

cmbaxter
cmbaxter

Reputation: 35463

You could try:

f1() orElse f2() orElse Option(f3()) getOrElse f4()

Assuming that f1 and f2 returns options of the same type and f3 and f4 return a non-options of that same type

EDIT

It's not completely clear from your example if f3() returns an Option or not, so if it does then the code would be simplified to:

f1() orElse f2() orElse f3() getOrElse f4()

Upvotes: 5

Related Questions