Closed jedesroches closed 2 months ago
In fact, thinking about it, there isn't even the need for bind (i.e. for ValidationResult to be a Monad), apply would be enough (i.e. for it to be an Applicative). Something along the lines of:
public fun <A, B> ValidationResult<(A) -> B>.apply(x: ValidationResult<A>): ValidationResult<B> =
when (this) {
is Invalid -> this
is Valid -> x.map(value)
}
Would allow one to compose various value class validations through smart constructors to build validated data classes, as so:
// If Foo and Bar are value classes with validation in their companion object invoke methods
data class FooBar(val foo: Foo, val bar: Bar) {
// Does this exist somehow in Kotlin ?
companion object { fun curried(foo: Foo) = { bar: Bar -> FooBar(foo, bar) } }
}
// And then this works:
val validatedFooBar: ValidationResult<FooBar> = Foo("abc").map(FooBar::curry).apply(Bar("cde"))
My kotlin is not good enough to make a DSL-y thing, but one could imagine a syntax like below to be rather nice to work with:
val validatedFooBar: ValidationResult<FooBar> = validateApply<FooBar> {
Foo("abc")
Bar("cde")
}
I'm not 100% sure I follow, but I think it would be good enough for ValidationResult
to be a functor. Then you can do:
val customer: Customer = ...
val dto: ValidationResult<CustomerDto>= customerValidation(customer).map { it.toDto() }
The current (0.4.0) map
doesn't properly work, fixed in 0.5.0. See #105 for when it will be released
To help you: In the JVM world functor is usually implemented by map
and monadic bind by flatMap
. Monadic unit is usually just a constructor. (Valid(...)
in konform). I purposefully have left out flatMap so far since I think it's not suitable to the design of the library, but you can easily implement it as an extension function if you want.
Trying to find these in types and using this nomenclature for issues will make things more clear. You will probably also enjoy Arrow (altough I personally don't think Kotlin is suitable for going so hard on the FP)
Closing this as it's not an issue per se, feel free to reply or open a new issue with a more directed question/problem
Hello, and thank you for this nice library. Coming from the FP world, I am trying to use it with the smart constructor pattern and, not having a bind (or flatMap, or andThen, or anything you call
(ValidationResult<T1> -> (T1 -> ValidationResult<T2>)) -> ValidationResult<T2>
), am having a bit of trouble: I'd be happy to open a PR with a bind implementation, but first I'm thinking maybe I'm using this wrong.Using Konform, I've managed to build the following inline class, who'se type represents a proof of being valid (I don't want to have
Valid<x>
's all over my domain code), which works well enough:But then when I have a class that is using the above value class as a member, I cannot compose both validations. Given:
Going from the first above to the second, I cannot do something like
CustomerID(id).andThen { id -> ... }
to build the final object. But I have to validate theCustomerID
before passing it to theCustomer
constructor (since theCustomerID
type enforces validity of the data it contains), and so I am left with no choice but to validate by hand the different elements, and compose the errors manually.What is the suggested usage pattern for Konform to have both type-level guarantees of data validity when passing values around and composability of validations to build more complex objects ?
I hope I've managed to explain my question correctly, and I'm looking forward to your answer! Have a great day :smiley: