【发布时间】:2013-12-02 16:04:18
【问题描述】:
假设我想写一个签名如下的方法:
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, _] 没有单子实例)。 p>
在我的第一个实现中,我始终使用ValidationNel,最后使用fold 作为一种假flatMap。第二,我在ValidationNel 和\/ 之间来回切换,这取决于我是需要错误累积还是单子绑定。它们产生相同的结果。
我在实际代码中使用了这两种方法,但还没有形成对其中一种的偏好。我错过了什么吗? 应该我更喜欢其中一个吗?
【问题讨论】:
-
ValidationNel 上的 flatMap 不会与第一个示例中的 fold 具有相同的结果吗?我知道验证不是 Monad,但它仍然有 flatMap。编辑:刚刚注意到 flatMap 在 7.1 中已被弃用,并附有使用
\\/的说明。看来还有第三种选择!我的回答是我只使用\/,因为我不想累积失败。在极少数情况下我会使用ValidationNel -
flatMaponValidation已弃用。 -
@drstevens:我经常想积累失败!当你例如处理包含数千个您不想一一识别错误的大型 JSON 文件的目录。
-
当然会在遍历
input: List[(String, String)]时积累错误。不过,您不会通过checkParses和checkValues累积错误。此外,checkValues永远不会返回Nel(InvalidSizes, InvalidSizes, ...),但类型签名表明它可以。我个人永远不会这样做,就像我永远不会List[a]当Option[a]更准确时一样。我发现这样做会使推理可能的错误范围变得更具挑战性。例如parseFormatBiffparseFormatA由于InvalidSizes而失败。 -
@drstevens:关于
checkValues的返回类型的公平点——这只是一个玩具示例,但我在这里进行了更改。问题的关键是我确实想在checkParses中积累——即如果这对中的任何一个成员都没有解析为整数,我希望看到这两个错误 - 并且在遍历列表时,但不要(不能)在checkValues中,它介于两者之间。
标签: validation scala functional-programming scalaz either