4

Suppose I need to write a validating function Validate[A]:

type Status[A] = Validation[List[String], A]
type Validate[A] = A => Status[A] // should be Kleisli

The function returns either a Success with the input if the input is valid or a Failure with the list of errors if it is not.

For example,

val isPositive: Validate[Int] = {x: Int =>
  if (x > 0) x.success else List(s"$x is not positive").failure 
}

val isEven: Validate[Int] = {x: Int =>
  if (x % 2 == 0) x.success else List(s"$x is not even").failure
}

Since Validation is a semigroup Validate is semigroup too and (if I define it as Kleisli) I can compose validating functions as follows:

val isEvenPositive = isEven |+| isPositive

Suppose now that I need to validate X:

case class X(x1: Int, // should be positive 
             x2: Int) // should be even

Since Validation is an applicative functor Validate is an applicative functor too.

val x: Validate[X] = (isPositive |@| isEven)(X.apply) 

Does it make sense ?

Michael
  • 41,026
  • 70
  • 193
  • 341
  • Validation only forms a semigroup where both left and right form semigroups – Daenyth May 29 '15 at 15:35
  • Thanks for reminding. I thought `Validation[E, A]` _requires_ `E` to be a semigroup but it probably does not. – Michael May 29 '15 at 15:40
  • It requires `E: Semigroup` to form an applicative and it requires `E: Semigroup, A: Semigroup` to form a semigroup. See [ValidationInstances](http://docs.typelevel.org/api/scalaz/stable/7.0.0/doc/scalaz/ValidationInstances.html) – Daenyth May 29 '15 at 15:42
  • Not a duplicate but somewhat related and might lead to a different approach: http://meta.plasm.us/posts/2013/06/05/applicative-validation-syntax/ – Daenyth May 29 '15 at 16:00

1 Answers1

1

You can compose validations when the left hand type is a semigroup. Your List works, but scalaz provides a built in type ValidationNel[L, R] = Validation[NonEmptyList[L], R] which you should use instead. There's several convenience functions like aValidation.toValidationNel for lifting values with single errors. The composed Validation will have a left hand side with all failures accumulated or a right hand with the success applied to your function

def foo: ValidationNel[String, Int]
def bar: ValidationNel[String, Double]
val composed: ValidationNel[String, Double] = foo(input) |@| bar(input) apply { (i: Int, d: Double) => i * d }

If you're looking for composition to a new single function in the shape of (V as shorthand for Validation)

(A => V[L, B], A => V[L, C]) => (A => V[L, (B, C)])

Then I'm not quite sure of the right way. It feels like there should be a combinator or two that would do just this but I haven't found it.

I have managed to write this composition, but I feel like there might be a better way.

  def composeV[A, B, C, L: Semigroup](f: A => Validation[L, B], g: A => Validation[L, C]): A => Validation[L, (B, C)] = {
    import scalaz.syntax.arrow._
    import scalaz.syntax.apply._
    import scalaz.std.function.function1Instance
    val oneInput: (A) => (Validation[L, B], Validation[L, C]) = f &&& g
    oneInput andThen {
      case (vb, vc) =>
        vb |@| vc apply { case x: (B, C) => x }
    }
  }

Here's another way:

def composeV[A, B, C, L: Semigroup](f: A => Validation[L, B], g: A => Validation[L, C]): A => Validation[L, (B, C)] = {
  import scalaz.syntax.apply._
  import scalaz.std.function.function1Instance
  f |@| g apply { case (vb, vc) =>
    vb |@| vc apply { case x: (B, C) => x }
  }
}
Daenyth
  • 35,856
  • 13
  • 85
  • 124
  • 1
    Yes, I am wondering how to compose _functions_ that return `Validation`. I think that since those functions are `Kleisli` we can compose them _exactly_ as `Validation` values. That's why I have asked this question. – Michael May 29 '15 at 15:09
  • @Michael I've just managed to write one. I'm sure there's a better way – Daenyth May 29 '15 at 15:13
  • I still think that since my `Validate` is `Kleisli` (I have updated the question) I get such a composition _for free_. – Michael May 29 '15 at 15:32
  • @Michael I agree, I think you can, but I can't figure it out – Daenyth May 29 '15 at 15:33
  • Ok, now I feel like I should go to REPL and try to code the solution _for real_ instead of asking in SO :) – Michael May 29 '15 at 15:35
  • @Michael I look forward to your answer, I've had this floating in my head for weeks without clicking on the right way – Daenyth May 29 '15 at 15:36
  • Sorry, cannot figure it out :( Maybe I will ask another question about that ? – Michael May 31 '15 at 14:16
  • Yes, definitely. Maybe something with `tuple` from apply syntax – Daenyth May 31 '15 at 14:18
  • Added a new question with another solution. You are welcome to check. – Michael Jun 01 '15 at 08:34