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

?

+3


source to share


2 answers


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.

+4


source


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).

0


source







All Articles