问题
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?
回答1:
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)
回答2:
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.
回答3:
There is the require
function for preconditions, which pretty much accomplishes what you want. You can do
def addPositive(a: Int, b: Int) =
require (a > 0 && b > 0, a + b)
There are part of scala.Predef, so they are always included. The parameter (a+b
in the example) is passed by name, so it will only be executed if the condition isn't true.
requires
is always activated, if you want to be able to deactivate it you can make it with assert
too, like :
assert(a > 0 && b > 0, a + b)
来源:https://stackoverflow.com/questions/15970145/functional-assertion-in-scala