假设我有几个案例类和函数来测试它们:

case class PersonName(...)
case class Address(...)
case class Phone(...)

def testPersonName(pn: PersonName): Either[String, PersonName] = ...
def testAddress(a: Address): Either[String, Address] = ...
def testPhone(p: Phone): Either[String, Phone] = ...

现在我定义了一个新的案例类 Person 和一个测试函数,它很快就会失败。
case class Person(name: PersonName, address: Address, phone: Phone)

def testPerson(person: Person): Either[String, Person] = for {
  pn <- testPersonName(person.name).right
  a <- testAddress(person.address).right
  p <- testPhone(person.phone).right
} yield person;

现在我希望函数 testPerson 来累积错误而不是快速失败。

我希望 testPerson 始终执行所有这些 test* 函数并返回 Either[List[String], Person] 。我怎样才能做到这一点 ?

最佳答案

Scala 的 for -comprehensions(对 flatMapmap 的调用组合进行脱糖)旨在允许您对 monadic 计算进行排序,以便您可以访问后续步骤的后续计算结果。考虑以下:

def parseInt(s: String) = try Right(s.toInt) catch {
  case _: Throwable => Left("Not an integer!")
}

def checkNonzero(i: Int) = if (i == 0) Left("Zero!") else Right(i)

def inverse(s: String): Either[String, Double] = for {
  i <- parseInt(s).right
  v <- checkNonzero(i).right
} yield 1.0 / v

这不会累积错误,实际上没有合理的方法可以。假设我们调用 inverse("foo") 。那么 parseInt 显然会失败,这意味着我们无法获得 i 的值,这意味着我们无法继续执行序列中的 checkNonzero(i) 步骤。

在您的情况下,您的计算没有这种依赖性,但是您使用的抽象(单子(monad)排序)不知道这一点。您想要的是 Either 之类的类型,它不是 monadic,但适用。有关差异的一些详细信息,请参阅 my answer here

例如,您可以使用 ScalazValidation 编写以下内容,而无需更改任何单独的验证方法:
import scalaz._, syntax.apply._, syntax.std.either._

def testPerson(person: Person): Either[List[String], Person] = (
  testPersonName(person.name).validation.toValidationNel |@|
  testAddress(person.address).validation.toValidationNel |@|
  testPhone(person.phone).validation.toValidationNel
)(Person).leftMap(_.list).toEither

虽然这当然比必要的更冗长并且会丢弃一些信息,但始终使用 Validation 会更简洁一些。

关于scala - 如何在两者中累积错误?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/21351391/

10-12 19:43