Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chain multiple functions in Scala while a condition holds

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")
like image 501
sanyi14ka Avatar asked May 11 '19 18:05

sanyi14ka


2 Answers

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)
  }
}
like image 60
Krzysztof Atłasik Avatar answered Sep 17 '22 23:09

Krzysztof Atłasik


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"))
like image 20
det0 Avatar answered Sep 19 '22 23:09

det0