假设我有几个案例类和函数来测试它们:
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(对 flatMap
和 map
的调用组合进行脱糖)旨在允许您对 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。例如,您可以使用 Scalaz 的
Validation
编写以下内容,而无需更改任何单独的验证方法: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/