Suppose I want to write a method with the following signature:
def parse(input: List[(String, String)]):
ValidationNel[Throwable, List[(Int, Int)]]
For each pair of strings in the input, it needs to verify that both members can be parsed as integers and that the first is smaller than the second. It then needs to return the integers, accumulating any errors that turn up.
First I'll define an error type:
import scalaz._, Scalaz._
case class InvalidSizes(x: Int, y: Int) extends Exception(
s"Error: $x is not smaller than $y!"
)
Now I can implement my method as follows:
def checkParses(p: (String, String)):
ValidationNel[NumberFormatException, (Int, Int)] =
p.bitraverse[
({ type L[x] = ValidationNel[NumberFormatException, x] })#L, Int, Int
](
_.parseInt.toValidationNel,
_.parseInt.toValidationNel
)
def checkValues(p: (Int, Int)): Validation[InvalidSizes, (Int, Int)] =
if (p._1 >= p._2) InvalidSizes(p._1, p._2).failure else p.success
def parse(input: List[(String, String)]):
ValidationNel[Throwable, List[(Int, Int)]] = input.traverseU(p =>
checkParses(p).fold(_.failure, checkValues _ andThen (_.toValidationNel))
)
Or, alternatively:
def checkParses(p: (String, String)):
NonEmptyList[NumberFormatException] / (Int, Int) =
p.bitraverse[
({ type L[x] = ValidationNel[NumberFormatException, x] })#L, Int, Int
](
_.parseInt.toValidationNel,
_.parseInt.toValidationNel
).disjunction
def checkValues(p: (Int, Int)): InvalidSizes / (Int, Int) =
(p._1 >= p._2) either InvalidSizes(p._1, p._2) or p
def parse(input: List[(String, String)]):
ValidationNel[Throwable, List[(Int, Int)]] = input.traverseU(p =>
checkParses(p).flatMap(s => checkValues(s).leftMap(_.wrapNel)).validation
)
Now for whatever reason the first operation (validating that the pairs parse as strings) feels to me like a validation problem, while the second (checking the values) feels like a disjunction problem, and it feels like I need to compose the two monadically (which suggests that I should be using /
, since ValidationNel[Throwable, _]
doesn't have a monad instance).
In my first implementation, I use ValidationNel
throughout and then fold
at the end as a kind of fake flatMap
. In the second, I bounce back and forth between ValidationNel
and /
as appropriate depending on whether I need error accumulation or monadic binding. They produce the same results.
I've used both approaches in real code, and haven't yet developed a preference for one over the other. Am I missing something? Should I prefer one over the other?
See Question&Answers more detail:
os