2

Cannot figure out if it is possible to write something like this using Scalaz 7. I have tried to express myself with the comments inside the code block.

def validate1(p: String) = ValidationNel[String, Value] = ...
def validate2(p: String) = ValidationNel[String, Value] = ...

validateCombination(p1: String, p2: String) = {
  // I would like to write something like
  (validate1(p1) |@| validate2(p2)) { (v1, v1) =>
    // And validate the combinations here and return successNel of failNel
  }
}

def validate(p1: String, p2: String, p3: String) = {
  (validateCombination(p1, p2) |@| validate1(p3)) { (v1, v2, v3) =>
    // Notice the three parameters I want to have here
  }
}

I just end up with different types of confusing compilation errors in validateCombinations or just 2 parameters for the applicative functor I get inside validate function, one of them being of type ValidationNel[...].

Lauri
  • 4,670
  • 2
  • 24
  • 17

1 Answers1

6

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.

Community
  • 1
  • 1
senia
  • 37,745
  • 4
  • 88
  • 129
  • Though I'd use the for comprehension -- it is easier to understand, imho. – Daniel C. Sobral Jun 04 '13 at 13:35
  • `Validation` doesn't have flatten because it isn't intended to have a monad. In fact, the `flatMap` method is dubious. But there are methods for running operations over the `\/` equivalent of the validation and converting back again, e.g. `v.disjunctioned(_.flatten)` – Ben James Jun 04 '13 at 13:48
  • @DanielC.Sobral: do you mean "for comprehension instead of `|@|`"? I thought the point is to keep `|@|` untouched. Thank you for suggestion. Do you mind I'll add it to my answer? – senia Jun 04 '13 at 13:51
  • Thanks for the nice answer. I actually first tried this using for comprehensions but I run into problems like "could not find implicit value for parameter M: scalaz.Monoid[scalaz.NonEmptyList[String]]" when writing code like for (v1 <- validate1(p1); v2 <- validate(p2); if (isValidCombination(v1, v2)) – Lauri Jun 04 '13 at 13:58
  • @Lauri: there is no method `withFilter` in [`Validation`](http://scalaz.github.io/scalaz/scalaz-2.9.0-1-6.0/doc.sxr/scalaz/Validation.scala.html), so you can't use `if` statement in for comprehension. See [this answer](http://stackoverflow.com/a/1059501/406435). – senia Jun 04 '13 at 14:19
  • I meant for comprehension instead of `flatMap`. – Daniel C. Sobral Jun 04 '13 at 17:03