user3001
user3001

Reputation: 3497

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

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.

Upvotes: 7

Views: 1711

Answers (6)

D3xter
D3xter

Reputation: 6465

There are couple of Haskell implementations, so let's try to solve it with Kotlin.

First, we start with the Data-Object:

class Fruit(val color: String, val weight: Int)

And we need a Type that represents a Fruit and whether an error occurred:

sealed class Result<out E, out O> {
    data class Error<E>(val e: E) : Result<E, Nothing>()

    data class Ok<O>(val o: O): Result<Nothing, O>()
}

Now lets define the Type of a FruitRule:

typealias FruitRule = (Fruit) -> String?

The FruitRule is a function that receives a Fruit-Instance and returns null if the rule passed or the error message.

The problem we got here is that FruitRule itself is not composable. So we need a Type that is composable and runs a FruitRule on a Fruit

typealias ComposableFruitRule = (Result<String, Fruit>) -> Result<String, Fruit>

First, we need a way to create a ComposableFruitRule from a FruitRule

fun createComposableRule(f: FruitRule): ComposableFruitRule {
    return { result: Result<String, Fruit> ->

        if(result is Result.Ok<Fruit>) {
            val temporaryResult = f(result.o)

            if(temporaryResult is String)
                Result.Error(temporaryResult)
            else
                //We know that the rule passed,
                //so we can return Result.Ok<Fruit> we received back
                result
        } else {
            result
        }
    }
}

createComposableFruitRule returns a lambda that first checks whether the provided Result is Result.Ok. If yes, it runs the provided FruitRule on the given Fruit and returns Result.Error if the error message is not null.

Now lets make our ComposableFruitRule composable:

infix fun ComposableFruitRule.composeRules(f: FruitRule): ComposableFruitRule {
    return { result: Result<String, Fruit> ->
        val temporaryResult = this(result)

        if(temporaryResult is Result.Ok<Fruit>) {
            createComposableRule(f)(temporaryResult)
        } else {
            temporaryResult
        }
    }
}

This infix-function composes a ComposableFruitRule together with a FruitRule, that means first the internal FruitRule is invoked. If there is no error, the FruitRule provided as a parameter is invoked.

So now we are able to compose FruitRules together and afterward just provide a Fruit and check the rules.

fun colorIsRed(fruit: Fruit): String? {
    return if(fruit.color == "red")
        null
    else
        "Color is not red"
}

fun notTooHeavy(fruit: Fruit): String? {
    return if(fruit.weight < 500)
        null
    else
        "Fruit too heavy"
}

fun main(args: Array<String>) {
    val ruleChecker = createComposableRule(::colorIsRed) composeRules ::notTooHeavy
    //We can compose as many rules as we want
    //e.g. ruleChecker composeRules ::fruitTooOld composeRules ::fruitNotTooLight

    val fruit1 = Fruit("blue", 300)
    val result1 = ruleChecker(Result.Ok(fruit1))
    println(result1)

    val fruit2 = Fruit("red", 700)
    val result2 = ruleChecker(Result.Ok(fruit2))
    println(result2)

    val fruit3 = Fruit("red", 350)
    val result3 = ruleChecker(Result.Ok(fruit3))
    println(result3)
}

The Output of that main is:

Error(e=Color is not red)
Error(e=Fruit too heavy)
Ok(o=Fruit@65b54208)

Upvotes: 2

danidiaz
danidiaz

Reputation: 27766

Is it Monads as my gut feeling tries to tell me that?

I think Monad is too strong a requirement in your case. Your validation functions

fun theFruitIsRed(fruit: Fruit) : Optional< String>

don't return an useable value when they validate successfully. And a defining characteristic of Monad is being able do decide what future computations to execute based on a previous result. "If the first validator succeeds returning foo, validate this field, if it succeeds returning bar, validate this other field instead".

I'm not knowledgeable about Kotlin, but I think you could have a Validator<T> class. It basically would wrap a single validation function for a type T that returned an Optional<String>.

Then you could write a method that combined two validators into a composite validator. The inner function of the composite validator would receive a T, run the first validator, return the error if it fails, if not run the second validator. (If your validators returned some useful result on successful validation, like say non-fatal warnings, you would need to supply an additional function to combine these results.)

The idea its that you would first compose the validators, and only then supply the actual T to get the final result. This compose-before-running approach is used by Java's Comparator, for example.

Notice that in this solution, even if your functions returned some result on successful validation, those values would not be used to select what validations to do next (though an error would stop the chain). You could combine results using a function but that's it. This "more rigid" style of programming is called Applicative in Haskell. All types supporting a Monad interface can be used in an Applicative way, but some types support Applicative without supporting Monad.


Another interesting aspect of validators is that they are contravariant on their input type T. This means that you can "pre-apply" a function from A to B to a Validator<B>, resulting in a Validator<A> whose type went "backwards" compared to the direction of the function. (The mapping function of Java's Collectors class works this way.)

And you can go further down this route by having functions that build a validator for a composite out of validators for their individual parts. (What in Haskell is called Divisible.)

Upvotes: 1

HTNW
HTNW

Reputation: 29193

Since the type of the object being validated can't change (as the object itself shouldn't change), I wouldn't use a monad (or any sort of functor). I'd have a type Validator a err = a -> [err]. If a validator succeeds, it outputs [] (no error). This forms a monoid, where mzero = const [] and mappend f g x = f x `mappend` g x. Haskell has this built in as instance Monoid b => Monoid (a -> b)

EDIT: I appear to have misread the question. @4castle's answer is almost exactly this one but uses Maybe err instead of [err]. Use that.

// Scala, because I'm familiar with it, but it should translate to Kotlin
case class Validator[-A, +Err](check: A => Seq[Err]) {
  def apply(a: A): Err = check(a)
  def |+|[AA >: A](that: Validator[A, Err]): Validator[AA, Err]
  = Validator { a =>
    this(a) ++ that(a)
  }
}
object Validator {
  def success[A, E]: Validator[A, E] = Validator { _ => Seq() }
}

type FruitValidator = Validator[Fruit, String]
val notTooHeavy: FruitValidator = Validator { fruit =>
  if(fruit.weight < 500) Seq() else Seq("Too heavy")
  // Maybe make a helper method for this logic
}

val isRed: FruitValidator = Validator { fruit =>
  if (fruit.color == "red") Seq() else Seq("Not red")
}

val compositeRule: FruitValidator = notTooHeavy |+| isRed

To use, just call a Validator like compositeRule(Fruit("green", 700)), which returns 2 errors in this case.

To see why the reader monad is inappropriate here, consider what happens if

type Validator = ReaderT Fruit (Either String) Fruit
ruleA :: Validator
ruleA = ReaderT $ \fruit ->
  if color fruit /= "red" then Left "Not red"
  else Right fruit
ruleB :: Validator
ruleB = ReaderT $ \fruit ->
   if weight fruit >= 500 then Left "Too heavy"
   else Right fruit
ruleC = ruleA >> ruleB

greenHeavy = Fruit "green" 700

Both ruleA and ruleB fail for greenHeavy, yet running runReaderT ruleC greenHeavy only produces the first error. This is undesirable: you likely want as many errors as possible revealed per run.

Also, you can "hijack" the validation:

bogusRule :: ReaderT Fruit (Either String) Int
bogusRule = return 42

ruleD = do ruleA
           ruleB
           bogusRule -- Validates just fine... then throws away the Fruit so you can't validate further.

Upvotes: 3

castletheperson
castletheperson

Reputation: 33496

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)

Upvotes: 4

Paul Johnson
Paul Johnson

Reputation: 17796

It sounds like you are looking for an error monad. Its like the Maybe (aka Option) monad, but the error case carries a message.

In Haskell its just the Either type, with the first argument being the type of the error value.

type MyError a = Either String a

If you check the Data.Either documentation you will see that Either e is already an instance of Monad, so you don't need to do anything else. You can just write:

notTooHeavy :: Fruit -> MyError ()
notTooHeavy fruit = 
    when (weight fruit > 500) $ fail "Too heavy"

What the monad instance does is stop the computation at the first fail, so you get e.g. Left "Too heavy" or Right (). If you want to accumulate errors then you have to do somthing more complicated.

Other posters have suggested that you don't need monads because your example code has all the functions returning (). While this may be true of your examples, I'm reluctant to generalise that fast. Also since you get the monadic instance automatically with Either it makes sense to just use it.

Upvotes: 1

leftaroundabout
leftaroundabout

Reputation: 120751

To answer generally the question

Now the problem is that somehow I have to carry the fruit context from check to check call.

...phrased as...

Given some monad M, how do I chain some M actions while also (implicitly) giving the same “context” object to each?

The Haskell answer would be to use the ReaderT monad transformer. It takes any monad, such as Maybe, and gives you another monad which implicitly passes a “global constant” to each action.

Let me rewrite your checkers in Haskell:

data Fruit = Fruit {colour::String, weight::Int}

theFruitIsRed :: Fruit -> Either String ()
theFruitIsRed fruit
  | colour fruit == "red"  = Right ()
  | otherwise              = Left "Fruit not red"

fruitNotTooHeavy :: Fruit -> Either String ()
fruitNotTooHeavy fruit
  | weight fruit < 500     = Right ()
  | otherwise              = Left "Too heavy"

Note that I've used Either String () instead of Maybe String because I want String to be the “abort case”, whereas in the Maybe monad it would be the “carry on” case.

Now, instead of doing

checks :: Fruit -> Either String ()
checks fruit = do
    theFruitIsRed fruit
    fruitNotTooHeavy fruit

I can do

checks = runReaderT $ do
    ReaderT theFruitIsRed
    ReaderT fruitNotTooHeavy

Your Result class seems to be essentially a special instantiation of the ReaderT transformer. Not sure if you could implement the exact thing in Kotlin as well.

Upvotes: 1

Related Questions