验证与分离

Tra*_*own 19 validation functional-programming scala either scalaz

假设我想编写一个带有以下签名的方法:

def parse(input: List[(String, String)]):
  ValidationNel[Throwable, List[(Int, Int)]]
Run Code Online (Sandbox Code Playgroud)

对于输入中的每对字符串,它需要验证两个成员都可以解析为整数,并且第一个成员小于第二个.然后它需要返回整数,累积任何出现的错误.

首先,我将定义一个错误类型:

import scalaz._, Scalaz._

case class InvalidSizes(x: Int, y: Int) extends Exception(
  s"Error: $x is not smaller than $y!"
)
Run Code Online (Sandbox Code Playgroud)

现在我可以按如下方式实现我的方法:

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))
  )
Run Code Online (Sandbox Code Playgroud)

或者,或者:

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
  )
Run Code Online (Sandbox Code Playgroud)

现在,无论什么原因,第一个操作(验证了对解析字符串形式)感觉我像一个验证问题,而第二个(检查值)感觉就像一个脱节的问题,它的感觉就像我需要组成两个monadically(这表明我应该使用\/,因为ValidationNel[Throwable, _]没有monad实例).

在我的第一个实现中,我使用了ValidationNel整个然后fold最后作为一种假flatMap.在第二个,我来回反弹之间ValidationNel,并\/视情况根据我是否需要误差积累或单子结合.它们产生相同的结果.

我在实际代码中使用了这两种方法,并且还没有开发出对一种方法的偏好.我错过了什么吗?我应该更喜欢一个吗?

drs*_*ens 10

这可能不是您正在寻找的答案,但我注意到Validation有以下方法

/** Run a disjunction function and back to validation again. Alias for `@\/` */
def disjunctioned[EE, AA](k: (E \/ A) => (EE \/ AA)): Validation[EE, AA] =
  k(disjunction).validation

/** Run a disjunction function and back to validation again. Alias for `disjunctioned` */
def @\/[EE, AA](k: (E \/ A) => (EE \/ AA)): Validation[EE, AA] =
  disjunctioned(k)
Run Code Online (Sandbox Code Playgroud)

当我看到它们时,在我记住这个问题之前,我真的看不出它们的用处.它们允许您通过转换为析取来进行适当的绑定.

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)): 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(checkValues(_).leftMap(_.wrapNel)))
  )
Run Code Online (Sandbox Code Playgroud)

  • +1,谢谢 - 我也从未关注过`@\/`,我比任何一种方法都更喜欢这种方法.不过,我会延长一点时间以获得更大的回答. (2认同)