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 ?

4
  • Validation only forms a semigroup where both left and right form semigroups Commented May 29, 2015 at 15:35
  • Thanks for reminding. I thought Validation[E, A] requires E to be a semigroup but it probably does not. Commented May 29, 2015 at 15:40
  • It requires E: Semigroup to form an applicative and it requires E: Semigroup, A: Semigroup to form a semigroup. See ValidationInstances Commented May 29, 2015 at 15:42
  • Not a duplicate but somewhat related and might lead to a different approach: meta.plasm.us/posts/2013/06/05/applicative-validation-syntax Commented May 29, 2015 at 16:00

1 Answer 1

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 }
  }
}
Sign up to request clarification or add additional context in comments.

9 Comments

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 I've just managed to write one. I'm sure there's a better way
I still think that since my Validate is Kleisli (I have updated the question) I get such a composition for free.
@Michael I agree, I think you can, but I can't figure it out
Ok, now I feel like I should go to REPL and try to code the solution for real instead of asking in SO :)
|

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.