Reputation: 3497
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.
Upvotes: 7
Views: 1711
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
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
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
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
]
Note that I'm taking advantage of the Monoid
instance of functions here:
Monoid b => Monoid (a -> b)
Upvotes: 4
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
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 someM
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