如何累积错误?

Mic*_*ael 11 functional-programming scala either

假设我有几个案例类和很少的函数来测试它们.

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

现在我定义一个新的案例类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;
Run Code Online (Sandbox Code Playgroud)

现在我想用testPerson 它来累积错误而不是快速失败.

我想函数testPerson总是执行所有这些test*函数并返回Either[List[String], Person].我怎样才能做到这一点 ?

Kev*_*ght 16

你想隔离test*方法并停止使用理解!

假设(无论出于什么原因)scalaz不适合你......可以在不必添加依赖项的情况下完成.

与许多scalaz示例不同,这是一个库不会比"常规"scala更多地减少冗长的示例:

def testPerson(person: Person): Either[List[String], Person] = {
  val name  = testPersonName(person.name)
  val addr  = testAddress(person.address)
  val phone = testPhone(person.phone)

  val errors = List(name, addr, phone) collect { case Left(err) => err }

  if(errors.isEmpty) Right(person) else Left(errors)      
}
Run Code Online (Sandbox Code Playgroud)


Tra*_*own 13

Scala的for-comprehensions(其中desugar来调用的组合flatMapmap)的设计,让你在,你有机会获得在后续步骤中较早计算的结果这样的方式排序一元计算.考虑以下:

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

这不会累积错误,实际上没有合理的方法.假设我们打电话inverse("foo").然后parseInt显然会失败,这意味着我们无法获得价值i,这意味着我们无法继续前进到checkNonzero(i)序列中的步骤.

在你的情况下,你的计算没有这种依赖,但你正在使用的抽象(monadic排序)并不知道.你想要的是一种Either类似于monadic的类型,但这是适用的.请参阅我的答案,了解有关差异的一些细节.

例如,你可以写下面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
Run Code Online (Sandbox Code Playgroud)

虽然当然这比必要的更冗长,并且丢弃了一些信息,并且Validation在整个过程中使用会更加清晰.