3

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?

14
  • I would start learning about monads, now, because you have a (textbook example of a) monad at hand. Commented Jun 1, 2015 at 8:51
  • 1
    I know a little bit about monads. I do not think I need monads here. What monad exactly do you mean ? Commented Jun 1, 2015 at 8:53
  • 1
    Status (Validation) is an applicative functor but not a monad. Commented Jun 1, 2015 at 9:05
  • 2
    @n.m. very short answer: Validation is not a monad because it is defined this way in scalaz. For more details see for example stackoverflow.com/questions/12211776/… Commented Jun 1, 2015 at 9:21
  • 2
    @n.m. Validation is not defined as a monad for a reason. You are welcome to read about it in any scalaz tutorial or ask on SO. Commented Jun 1, 2015 at 9:31

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Note that if you wanted to use the validated values (as in your implementation) you could write this: x => (isPositive(x.x1) tuple isEven(x.x2)).map((X.apply _).tupled). I find the as(x) version clearer, though.
0

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

4 Comments

My checkX does accumulate errors. You can try checkX(new X(-1, 1)) in REPL and see the result Status[X] = Failure(NonEmptyList(-1 not positive, 1 not even)).
I don't want to use monads in this example. Applicative (which is not a monad) seems to fit better,
I agree that it's easy to overuse Validation, but there are lots of legitimate use cases, and this sounds like one of them. I don't understand how the objection in the first paragraph applies here.
Ah - I missed that you were applying the function to different xs - in my defence you said "composition", which to my mind means applying one function to the result of another. In this case I think the "elegant" solution is to use a shapeless Poly that knows how to handle each field separately (i.e. case class X(x1: Int @@ WidgetCount, x2: Int @@ FooCheck), and then have cases in your Poly for the two different types), and then use shapeless' traverse on case classes. (But that's a lot of machinery that may not be worth it).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.