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.
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.
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.
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.
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())
}
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.
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()
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