Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functional programming: How to carry on the context for a chain of validation rules

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

  • to carry the fruit context when invoking the functions
  • so that I can chain (basically: compose) multiple checks in a row using the the same method
  • along with the results of the rule functions without changing the interface of these.
  • without side-effects

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.

like image 937
user3001 Avatar asked Oct 03 '17 19:10

user3001


1 Answers

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)
like image 71
4castle Avatar answered Sep 29 '22 10:09

4castle