You could use .flatMap(identity) in method validateCombination to produce ValidationNel[String, (Value, Value)] and pattern matching in method validate like this:
def validateCombination(p1: String, p2: String): ValidationNel[String, (Value, Value)] = {
// I would like to write something like
(validate1(p1) |@| validate2(p2)) { (v1, v1) =>
(v1, v2).successNel[String]
}.flatMap(identity)
}
def validate(p1: String, p2: String, p3: String) = {
(validateCombination(p1, p2) |@| validate1(p3)) { case ((v1, v2), v3) =>
// Notice the three parameters I want to have here
}
}
flatMap(identity)
Normally you would use method flatten on nested containers to get M[T] from M[M[T]]. It works on Future, Option, Try, collections and so on.
In this case type M[T] = ValidationNel[String, T].
I don't know why there is no method flatten in Validation, but you could always use flatMap(identity) instead of flatten.
match
As Ben James noted, flatMap on Validation is dubious. You could always use match instead of it:
(validate1(p1) |@| validate2(p2)) { (v1, v1) =>
(v1, v2).successNel[String]
} match {
case Success(s) => s
case Failure(f) => Failure(f)
}
pattern matching
Pattern matching is the common way to deal with tuples. For instance it's vary useful with foldLeft method, like foldLeft(1 -> 2){ case ((a, b), c) => ??? }.
If you find yourself using getters _N on Tuple you are probably should use pattern matching.
for comprehension
As Daniel C. Sobral noted for comprehension could be easier to understand.
You could use it in your validate method like this:
def validate(p1: String, p2: String, p3: String) = {
for{
(v1, v2) <- validateCombination(p1, p2) // pattern matching
v3 <- validate1(p3)
} yield ??? // Your code here
}
It involves pattern matching without case keyword.
Note that for comprehension calls flatMap on validateCombination(p1, p2), so you'll lost error messages from validate1(p3) in case validateCombination(p1, p2) is Failure. On the contrary, |@| collects all error messages from both sides.