假设我想编写一个具有以下签名的方法:
def parse(input: List[(String, String)]):
ValidationNel[Throwable, List[(Int, Int)]]
对于输入中的每一对字符串,它需要验证两个成员都可以解析为整数,并且第一个小于第二个。然后它需要返回整数,累积出现的任何错误。
首先,我将定义一个错误类型:
import scalaz._, Scalaz._
case class InvalidSizes(x: Int, y: Int) extends Exception(
s"Error: $x is not smaller than $y!"
)
现在我可以按如下方式实现我的方法:
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))
)
或者,或者:
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
)
现在无论出于何种原因,第一个操作(验证对解析为字符串)对我来说就像一个验证问题,而第二个操作(检查值)感觉就像一个析取问题,感觉我需要组合这两个单子(这表明我应该使用\/
,因为ValidationNel[Throwable, _]
没有 monad 实例)。
在我的第一个实现中,我ValidationNel
始终使用,然后fold
在最后作为一种假flatMap
. 第二,我根据是否需要错误累积或单子绑定在适当的情况下来ValidationNel
回反弹。\/
它们产生相同的结果。
我已经在实际代码中使用了这两种方法,但还没有形成对其中一种的偏好。我错过了什么吗?我应该更喜欢一个吗?