I have a set of functions (rules) for checking, which take the context as a parameter and either return "Okay" or "Error" with the message. Basically they can return type Maybe
(Haskell) / Optional
(Java).
In the following case, I would like to check the properties Fruit
(context) and return an error message if the check fails, otherwise "Okay" / Nothing.
Note: I would prefer a solution that is purely functional style and stagnant / immutable. Actually this is a little Kata.
In my experiments I used Kotlin, but the main problem also applies to any language that supports higher order functions (such as Java and Haskell). You can find the link to the full source code here and the same thing at the very bottom.
Given the Fruit class with color and weight, as well as some sample rules:
data class Fruit(val color:String, val weight:Int)
fun theFruitIsRed(fruit: Fruit) : Optional<String> =
if (fruit.color == "red") Optional.empty() else Optional.of("Fruit not red")
fun fruitNotTooHeavy(fruit: Fruit) : Optional<String> =
if (fruit.weight < 500) Optional.empty() else Optional.of("Too heavy")
Now I would like to bind the rule score using the link to the corresponding function, without specifying the context as an argument with FruitRuleProcessor
. When rule processing fails, it should not evaluate any other rules.
Example:
fun checkRules(fruit:Fruit) {
var res = FruitRuleProcessor(fruit).check(::theFruitIsNotRed).check(::notAnApple).getResult()
if (!res.isEmpty()) println(res.get())
}
def main(args:Array<String) {
checkRules(Fruit("green","200"))
checkRules(Fruit("red","1000"))
}
I don’t care where it failed, only about the result. Also, when a function returns an error, others should not be processed. Again, this is pretty much like Optional
Monad.
, - Fruit
check
check
.
- Result
, RuleError(context:Fruit, message:String)
Okay(context)
. Optional
, Fruit
( T = Fruit
)
sealed class Result<T>(private val context:T) {
fun isError () = this is RuleError
fun isOkay() = this is Okay
infix fun check(f: (T) -> Result<T>) : Result<T> {
return if (isError()) this else f(context)
}
class RuleError<T>(context: T, val message: String) : Result<T>(context)
class Okay<T>(context: T) : Result<T>(context)
}
, /, return
, a Fruit
Result
or
, bind
. Scala Haskell, , .
fun theFruitIsNotTooHeavy(fruit: Fruit) : Result<Fruit> =
if (fruit.weight < 500) Result.Okay(fruit) else Result.RuleError(fruit, "Too heavy")
fun theFruitIsRed(fruit: Fruit) : Result<Fruit> =
if (fruit.color == "red") Result.Okay(fruit) else Result.RuleError(fruit, "Fruit not red")
, :
fun checkRules(fruit:Fruit) {
val res = Result.Okay(fruit).check(::theFruitIsRed).check(::theFruitIsNotTooHeavy)
if (res.isError()) println((res as Result.RuleError).message)
}
//:
: Fruit
, .
, :
? , ?
, Kotlin Java 8 ( ), (, Scala Haskell) . ( , :))
this fiddle.