Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 590
David Moles Avatar asked Apr 24 '15 17:04

David Moles


People also ask

What is Option [] in Scala?

The Option in Scala is referred to a carrier of single or no element for a stated type. When a method returns a value which can even be null then Option is utilized i.e, the method defined returns an instance of an Option, in place of returning a single object or a null.

How do you use some and options in Scala?

An Option[T] can be either Some[T] or None object, which represents a missing value. For instance, the get method of Scala's Map produces Some(value) if a value corresponding to a given key has been found, or None if the given key is not defined in the Map.

Why do we use option in Scala?

Scala's Option is particularly useful because it enables management of optional values in two self-reinforcing ways: Type safety – We can parameterize our optional values. Functionally aware – The Option type also provides us with a set of powerful functional capabilities that aid in creating fewer bugs.


3 Answers

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())
}
like image 115
Regis Blanc Avatar answered Nov 15 '22 05:11

Regis Blanc


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.

like image 44
netchkin Avatar answered Nov 15 '22 07:11

netchkin


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()
like image 23
cmbaxter Avatar answered Nov 15 '22 05:11

cmbaxter