Reputation: 42100
This is a follow-up to my previous question
Suppose I have two validating functions that return either the input if it is valid or the error messages if it is not.
type Status[A] = ValidationNel[String, A]
val isPositive: Int => Status[Int] =
x => if (x > 0) x.success else s"$x not positive".failureNel
val isEven: Int => Status[Int] =
x => if (x % 2 == 0) x.success else s"$x not even".failureNel
Suppose also that I need to validate an instance of case class X
:
case class X(x1: Int, // should be positive
x2: Int) // should be even
More specifically I need a function checkX: X => Status[X]
. Moreover, I'd like to write checkX
as a composition of isPositive
and isEven
.
val checkX: X => Status[X] =
({x => isPositive(x.x1)} |@| {x => isEven(x.x2)}) ((X.apply _).lift[Status])
Does it make sense ?
How would you write checkX
as a composition of isPositive
and isEven
?
Upvotes: 3
Views: 280
Reputation: 139058
There are lots of ways to write this, but I like the following:
val checkX: X => Status[X] = x => isPositive(x.x1).tuple(isEven(x.x2)).as(x)
Or:
val checkX: X => Status[X] =
x => isPositive(x.x1) *> isEven(x.x2) *> x.point[Status]
The key point is that you want to run the two validations only for their "effects" and then return the original value in the new context. This is a perfectly legitimate applicative operation, as your own implementation shows. There are just some slightly nicer ways to write it.
Upvotes: 4
Reputation: 17431
It doesn't make sense, because the two functions can't really be composed. You'd (presumably) like to check whether x
is positive and whether x
is even - but in the case where it is both negative and odd, you'd like to receive both errors. But that can't ever happen as a composition of those two functions - once you apply either of the failing cases, you don't have an x
any more to pass to the second function.
In my experience Validation
is almost never the correct type to use, for this very reason.
If you want "fail-fast" behaviour where the result is either success or the first error, you should use \/
(i.e. type Status[A] = String \/ A
in this case). If you want "accumulate all the error messages alongside the value" behaviour, you want Writer
, i.e. type Status[A] = Writer[Vector[String], A]
. Both of these types allow easy composition (because they have monad instances available) using e.g. Kleisli: Kleisli(isPositive) >==> isEven
would work for either of these definitions of Status
(but not for yours).
Upvotes: 0