问题
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 ?
回答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 }
}
}
来源:https://stackoverflow.com/questions/30527740/composing-validating-functions-in-scala