I have a set of functions (rules) for validation which take a context as parameter and either return "Okay" or an "Error" with a message. Basically these could return a Maybe
(Haskell) / Optional
(Java) type.
In the following I would like to validate properties of a Fruit
(the context) and return an error message if the validation failed, otherwise "Okay"/Nothing.
Note: I would prefer a solution that is purely functional style and stateless/immutable. It is a bit of a Kata, actually.
For my experiments I used Kotlin, but the core problem also applies to any language that supports higher order functions (such as Java and Haskell). You can find a link to the full source code here and the same at the very bottom.
Given a Fruit class with color and weight, plus some example 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 chain the rule evaluation using a reference to the respective function, without specifying the context as an argument using a FruitRuleProcessor
.
When processing a rule fails, it should not evaluate any of the other rules.
For 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) {
// "Fruit not red": The fruit has the wrong color and the weight check is thus skipped
checkRules(Fruit("green","200"))
// Prints "Fruit too heavy": Color is correct, checked weight (too heavy)
checkRules(Fruit("red","1000"))
}
I do not care where it failed, only about the result. Also when a function returns an error, the others should not be processed.
Again, this pretty much sounds like an Optional
Monad.
Now the problem is that somehow I have to carry the fruit
context from check
to check
call.
One solution I tried is to implement a Result
class that takes a context as value and has two subclasses RuleError(context:Fruit, message:String)
and Okay(context)
. The difference to Optional
is that now I can wrap around the Fruit
context (think T = Fruit
)
// T: Type of the context. I tried to generify this a bit.
sealed class Result<T>(private val context:T) {
fun isError () = this is RuleError
fun isOkay() = this is Okay
// bind
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)
}
I think that this looks like a monoid/Monad, with return
in the constructor lifting a Fruit
into a Result
and or
being the bind
. Although I tried some Scala and Haskell, admittedly I am not so experienced with that.
Now we can change the rules to
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")
which allows to chain checks like intended:
fun checkRules(fruit:Fruit) {
val res = Result.Okay(fruit).check(::theFruitIsRed).check(::theFruitIsNotTooHeavy)
if (res.isError()) println((res as Result.RuleError).message)
}
// Prints: Fruit not red Too heavy
However this has one major drawback: The Fruit
context now becomes part of the validation result, although it is not strictly necessary in there.
So to wrap it up: I'm looking for a way
fruit
context when invoking the functions What functional programming patterns could solve this problem? Is it Monads as my gut feeling tries to tell me that?
I would prefer a solution that can be done in Kotlin or Java 8 (for bonus points), but answers in other languages (e.g. Scala or Haskell) also could be helpful. (It's about the concept, not the language :) )
You can find the full source code from this question in this fiddle.
You could use/create a monoid wrapper of your Optional
/Maybe
type such as First
in Haskell which combines values by returning the first non-Nothing value.
I don't know Kotlin, but in Haskell it would look like this:
import Data.Foldable (foldMap)
import Data.Monoid (First(First, getFirst))
data Fruit = Fruit { color :: String, weight :: Int }
theFruitIsRed :: Fruit -> Maybe String
theFruitIsRed (Fruit "red" _) = Nothing
theFruitIsRed _ = Just "Fruit not red"
theFruitIsNotTooHeavy :: Fruit -> Maybe String
theFruitIsNotTooHeavy (Fruit _ w)
| w < 500 = Nothing
| otherwise = Just "Too heavy"
checkRules :: Fruit -> Maybe String
checkRules = getFirst . foldMap (First .)
[ theFruitIsRed
, theFruitIsNotTooHeavy
]
Ideone Demo
Note that I'm taking advantage of the Monoid
instance of functions here:
Monoid b => Monoid (a -> b)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With