假设我想编写一个具有以下签名的方法:
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, _]
没有单调递增实例)。
在我的第一个实现中,我始终使用ValidationNel
,然后在最后使用fold
作为一种伪flatMap
。在第二个实现中,根据需要在ValidationNel
和\/
之间反复跳转,具体取决于我是否需要错误累积或单调绑定。它们产生相同的结果。
我曾在实际代码中使用过这两种方法,但还没有发展出偏好。我错过了什么吗?我应该偏好其中一种吗?
\\/
的说明。看起来会有第三个选项!我的答案是我只使用\/
,因为我不想累积失败。我只在极少数情况下使用ValidationNel。 - drstevensinput: List[(String, String)]
时,我当然会积累错误。但是你在checkParses
和checkValues
中没有积累错误。此外,checkValues
永远不会返回Nel(InvalidSizes, InvalidSizes, ...)
,但类型签名表明它可能会这样做。就像我永远不会使用List[a]
而更准确的是使用Option[a]
一样,我个人永远不会这样做。我发现这样做使得推断可能出现的错误范围更具挑战性。例如,当parseFormatA
因为InvalidSizes
失败时,parseFormatB
才会执行。 - drstevenscheckValues
的返回类型的观点很公正——这只是一个玩具示例,但我已经在这里进行了更改。问题的关键点是我确实想在checkParses
中累积——即如果一对中的成员都不能解析为整数,则我想看到两个错误——并且在遍历列表时,但在checkValues
中不行(也不能),因为它位于其中间。 - Travis Brown