Suppose we have 3 methods which all return an Option
import scala.util.Try
def divideSafe(d: Int): Option[Int] = Try(42 / d).toOption
def sqrtSafe(x: Double): Option[Double] = if(!math.sqrt(x).isNaN) Some(math.sqrt(x)) else None
def convertSafe(s: String): Option[Int] = Try(s.toInt).toOption
Now I want to chain them while a condition holds. In my case, if the result from the previous method is defined then should go to the next one. And as long as the condition is false, then stop the operation and return a default value.
I can obtain the desired result with nested if-else statements:
def superSafe(d: Int, x: Double, s: String): Unit = {
if (divideSafe(d).isDefined) {
println(divideSafe(d).get.toString)
if (sqrtSafe(x).isDefined) {
println(sqrtSafe(x).toString)
if (convertSafe(s).isDefined) {
println(convertSafe(s).get.toString)
} else {
println("Converting failed")
}
} else {
println("Sqrt failed")
}
} else {
println("Dividing failed")
}
}
Therefore:
superSafe(1, -1, "5")
would print 42 and Sqrt failed
superSafe(0, 4, "cat")
would print Dividing failed
superSafe(42, 4, 1)
would print 1,2,1
However I want to avoid the nested if-else statements and I am interested if there is a functional way in Scala to solve this kind of problems.
I want something like orElse
statement, but the other way around.
The desired function could be used like this:
divideSafe(42) ifThen("Dividing failed") sqrtSafe(-4) ifThen("Sqrt failed") convertSafe("cat") ifThen("Converting failed")
You're using Option
which is not best suited for your use case. I believe Either
would work better in your example because it can hold information about failure(Left
) or about success (Right
). If you rewrote your functions to use Either
it might look like:
def divideSafe(d: Int): Either[String, Int] = Try(42 / d).toOption.toRight("Divide failed")
def sqrtSafe(x: Double): Either[String, Double] = math.sqrt(x) match {
case s if s.isNaN => Left("Sqrt failed") //you can use pattern matching to calculate square just once
case s => Right(s)
}
def convertSafe(s: String): Either[String, Int] = Try(s.toInt).toOption.toRight("Converting failed")
And then you could use for comprehension
in your superSafe
method:
def superSafe(d: Int, x: Double, s: String): Unit = {
val r: Either[String, (Int, Double, Int)] = for { //for-comprehension shortcircuits
r1 <- divideSafe(d) //if divideSafe fails it whole comprehension will return Left(Divide failed)
r2 <- sqrtSafe(x) //if it succeds it will go further
r3 <- convertSafe(s)
} yield (r1,r2,r3)
r match {
case Right((r1,r2,r3)) => { //you could move it inside yield, I splitted it for clarity
println(r1)
println(r2)
println(r3)
}
case Left(e) => println(e)
}
}
If you like it more functional, you can try this:
import scala.util.Try
def divideSafe(d: Int): Option[Int] = Try(42 / d).toOption
def sqrtSafe(x: Double): Option[Double] = if(!math.sqrt(x).isNaN) Some(math.sqrt(x)) else None
def convertSafe(s: String): Option[Int] = Try(s.toInt).toOption
def superSafe(d: Int, x: Double, s: String): Any = {
divideSafe(d)
.map(rd => sqrtSafe(x)
.map(rx => convertSafe(s)
.map(rs => (rd, rx, rs))
.getOrElse((rd, rx, "Converting failed")))
.getOrElse((rd, "Sqrt failed")))
.getOrElse("Dividing failed")
}
println(superSafe(1, 1, "1"))
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