Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functional assertion in Scala

Is there built-in support for assertions that return a result?

It is very non-functional to do this:

  def addPositive(a: Int, b: Int) = {
    assert(a > 0 && b > 0)
    a + b
  }

I would rather do something similar to:

  def addPositive(a: Int, b: Int) = 
    assert(a > 0 && b > 0)(a + b)

In this way I can avoid the imperative aspect of asserts. (the latter does not compile) Is anything similar available?

like image 287
Felix Avatar asked Dec 08 '22 16:12

Felix


2 Answers

Functional programming treats functions as pure mathematical functions (ideally). So what's the mathematics' way of saying a function doesn't work for some parameters and must blow up ?

Partial Functions

It turns out that Scala has a pretty good support for this concept: PartialFunction. This is how you would rewrite your code using partial functions:

val addPositive: PartialFunction[(Int, Int), Int] = {
  case (a, b) if a > 0 && b > 0 => a + b
}

This has several benefits:

If you call it with the wrong parameters it will throw a MatchError exception.

addPositive(-1, 2) => Exception in thread "main" scala.MatchError: (-1,2) (of class scala.Tuple2$mcII$sp)

You can actually sample the function's domain to check if some values are well suited as arguments for the function:

addPositive.isDefinedAt(-1, 2) => false

If you would like to apply the function to some arguments and get either a result, or some value indicating failure, you can lift it to return Option

addPositive.lift(-1, 2) => None
addPositive.lift(1, 2) => Some(12)

You can compose it with other functions to provide fallback in case of invalid arguments:

val fallback: PartialFunction[(Int, Int), Int] = { case (a, b) => Int.MinValue }
val f = addPositive orElse fallback

f(-1, 2) => -2147483648

Or to treat errors in a custom way:

val raiseError: PartialFunction[(Int, Int), Int] = {
  case (a, b) => throw new IllegalArgumentException(s"Cannot apply addPositive to arguments $a and $b")
}
val g = addPositive orElse raiseError

g(-1, 2) => Exception in thread "main" java.lang.IllegalArgumentException: Cannot apply addPositive to arguments -1 and 2

It works well with the standard lib: see Seq.collect and Seq.collectFirst.

Also PartialFunction is a normal unary function, so you inherit all the function operation as well.

Here is an article explaining very elegantly partial functions in Scala:

Scala partial functions (without a PhD)

like image 111
Marius Danila Avatar answered Dec 21 '22 09:12

Marius Danila


You could roll out your own implementation:

def assert[T](cond: =>Boolean)(expr: =>T): T = {
  assert(cond)
  expr
}

You could also use the option type to avoid exceptions, but that means that you'd later have to pattern match on the result:

def addPositive(a: Int, b: Int): Option[int] =
  if (a > 0 && b > 0) Some(a + b)
  else None

This can be refactored in the similar manner as the assert variant above.

like image 23
axel22 Avatar answered Dec 21 '22 10:12

axel22