How do I create functions that return validation?
This is a follow-up to my previous question
Suppose I have two validation functions that return either an input if it is valid, or 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
Let's also assume that I need to check an instance case class X
:
case class X(x1: Int, // should be positive
x2: Int) // should be even
In particular, I need a function checkX: X => Status[X]
. Moreover, I would like to write checkX
as a composition 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 do you write checkX
both composition isPositive
and isEven
?
source to share
There are many 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 two checks just for your "effects" and then return the original value in a new context. This is a perfectly legal applied operation, as your own implementation shows. There are only a few nicer ways to write it.
source to share
It doesn't make sense because the two functions cannot be linked. You (presumably) would like to check if it is x
positive and even x
, but in the case where it is negative and odd, you would like to get both errors. But this can never happen as the composition of these two functions - after applying either of the failed cases, you no longer have x
to go to the second function.
In my experience, it is Validation
almost never used by the correct type. That's why.
If you want "failing" behavior, where the result is either success or the first error, you should use \/
(ie, type Status[A] = String \/ A
in this case). If you want to "copy all error messages along with the behavior" you want Writer
, i.e. type Status[A] = Writer[Vector[String], A]
... Both of these types make it easy to create compositions (since they have monad instances available), for example Kleisli: Kleisli(isPositive) >==> isEven
will work for either of these definitions Status
(but not yours).
source to share