1

I have a method validateSyntax that throws an exception if the syntax of a statement is invalid, but does not need to return any information if the syntax is correct.

def validateSyntax(statement: String): Unit = {
  if (! is_valid(statement)) throw new Exception("syntax is not valid")
}

Then I call it from the Main:

val myStatements = List("foo =! bar", "print(foo + bar)")
myStatements.foreach(stmt => validateSyntax(stmt))

I want that my program exits and throws the exception if one of the statements is invalid

For the moment I set the return type of validateSyntax to Unit but I am not sure it is the functional way to do it.

Is there any more idiomatic way to do it ?

Ayoub Omari
  • 806
  • 1
  • 7
  • 24
  • Could you please explain the downvote so that I don't repeat the mistake ? – Ayoub Omari Mar 11 '20 at 15:36
  • 1
    I didn't downvote. But you might want to check [mcve] and [how to ask](https://www.stackoverflow.com/help/how-to-ask). To get good feedback, you need to show your code. – jrook Mar 11 '20 at 16:17
  • I supposed my question concerns only the signature of the function and one does not need to know its body. I edited my question with minimal code that illustrates my case – Ayoub Omari Mar 11 '20 at 16:25
  • Throwing an exception is a side effect and as such your code is not functional. You may refactor this using **Either** or **Try** or even **Option** but we would need to know more about your use case. – Luis Miguel Mejía Suárez Mar 11 '20 at 16:32
  • @LuisMiguelMejíaSuárez I edited my description – Ayoub Omari Mar 11 '20 at 16:39
  • You can wrap exceptions in a `scala.util.Try`. Take a look at the example in [scala.util.Try](https://www.scala-lang.org/api/current/scala/util/Try.html) page. That should get you started. – jrook Mar 11 '20 at 16:44
  • 1
    See [this answer](https://stackoverflow.com/questions/29779135/what-is-the-difference-between-try-and-either) to understand all the differences between the options you have to handle this functionally. – jrook Mar 11 '20 at 16:53

3 Answers3

3

Throwing exception is not idiomatic way to deal with errors in Scala, because of variety of reasons, like:

  • Scala does not have checked exceptions concept like Java, all exceptions from Scala perspective are unchecked - so compiler will not force you to handle certain exception, hence it easier to do a mistake and forget to handle exception;
  • Exceptions considered as side effects and function throwing them are considered as impure. It does not mean they bad or not properly designed, it means, they should be handled in a special manner, hence might make your code more complicated without meaningful reason. (it is long discussion out of scope of this question);
  • Error should be an return value - if you would like to force client side to handle expected error, proper result type should be choose;

So in you case you can go with next possible options:

In case of using plain Either the solution will look like:

  def validateSyntax(statement: String): Either[String, Unit] = {
    if (!is_valid(statement)) Left("syntax is not valid") else Right(())
  }

  val myStatements = List("foo =! bar", "print(foo + bar)")
  val validatedStatements = myStatements.map(statement => statement -> validateSyntax(statement))
  val allErrors = validatedStatements.collect {
    case (statement, Left(error)) => s"Statement: `$statement` is invalid because of $error"
  }

  if(allErrors.nonEmpty) {
    println(s"Statements not valid. All errors:\n - ${allErrors.mkString("\n - ")}")
    sys.exit(-1)
  }

which produce next example output:

Statements not valid. All errors:
 - Statement: `foo =! bar` is invalid because of syntax is not valid
 - Statement: `print(foo + bar)` is invalid because of syntax is not valid

If you would like to produce more precise error message, and validate each statement over more then one rule, then cats Validated mentioned before might be the option to go. Please, see implementation example below:

  import cats.data._
  import cats.data.Validated._
  import cats.instances.string._
  import cats.instances.unit._
  import cats.syntax.show._
  import cats.syntax.validated._
  import cats.syntax.foldable._

  type InvalidNec[T] = Invalid[NonEmptyChain[T]]

  type ValidatedStatement = (String, ValidatedNec[String, String])
  type InvalidStatement = (String, InvalidNec[String])

  implicit def listShow[T](implicit show: Show[T]): Show[List[T]] = Show.show(_.map(show.show).mkString("\n"))
  implicit val invalidNecShow: Show[InvalidNec[String]] = Show.show(_.e.mkString_("\n - ", "\n - ", ""))
  implicit val invalidStatementShow: Show[InvalidStatement] = Show.show {
    case (statement, invalid) =>  show"Statement `$statement` is invalid, because of next errors: $invalid"
  }

  def validateRule1(statement: String): ValidatedNec[String, Unit] = {
    val validCondition: Boolean = false // put validation condition here
    if(validCondition) ().validNec else "Statement does not satisfy first rule1".invalidNec
  }

  def validateRule2(statement: String): ValidatedNec[String, Unit] = {
    val validCondition: Boolean = false // put validation condition here
    if(validCondition) ().validNec else "Statement does not satisfy first rule2".invalidNec
  }

  def validateSyntax(statement: String): ValidatedNec[String, String] = {
    (validateRule1(statement) combine validateRule2(statement)).map(_ => statement)
  }

  val myStatements = List("foo =! bar", "print(foo + bar)")
  val validatedStatements: List[ValidatedStatement] = myStatements.map(statement => statement -> validateSyntax(statement))

  val invalidStatements: List[InvalidStatement] = validatedStatements.collect {
    case (statement, invalid: InvalidNec[String]) => statement -> invalid
  }

  if (invalidStatements.nonEmpty) {
    println(show"$invalidStatements")
    sys.exit(-1)
  }

Which produce next output result (just an example):

Statement `foo =! bar` is invalid, because of next errors: 
 - Statement does not satisfy first rule1
 - Statement does not satisfy first rule2
Statement `print(foo + bar)` is invalid, because of next errors: 
 - Statement does not satisfy first rule1
 - Statement does not satisfy first rule2

Hope this helps!

Ivan Kurchenko
  • 4,043
  • 1
  • 11
  • 28
1

You can use Either to encode errors on the Left and a valid result on the Right.

def validateSyntax(statement: String): Either[String, Unit] = {
  if (is_valid(statement)) Right(())
  else Left("syntax is not valid")
}
1

I would do something like this:

type Error = String
type Statement = String
type Statements = List[Statement]
type ErrorOr[A] = Either[Error, A]

def validateSyntax(statement: Statement): Option[Error] =
  if (is_valid(statement)) None
  else Some("syntax is not valid")

def ensureAllAreValid(statements: Statements): ErrorOr[Statements] = {
  @annotation.tailrec
  def loop(remaining: Statements, acc: Statements): ErrorOr[Statements] =
    remaining match {
      case statement :: tail =>
        validateSyntax(statement) match {
          case Some(error) =>
            Left(error)

          case None =>
            loop(
              remaining = tail,
              statement :: acc
            )
        }

      case Nil =>
        // If the order is not important,
        // Remove the reverse which is somewhat costly.
        Right(acc.reverse)
    }

    loop(remaining = statements, acc = List.empty)
  }
}

def main(): Unit = {
  val myStatements = List("foo =! bar", "print(foo + bar)")
  ensureAllAreValid(myStatements) match {
    case Left(error) =>
      println(error)
      system.exit(-1)

    case Right(statements) =>
      ???
  }
}