如何累积错误?

时间:2014-01-25 13:51:05

标签: scala functional-programming either

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

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

def tesPersonName(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]。我怎么能这样做?

4 个答案:

答案 0 :(得分:15)

您希望隔离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)      
}

答案 1 :(得分:13)

Scala的for - 理解(对于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)步骤。

在您的情况下,您的计算没有这种依赖性,但您正在使用的抽象(monadic排序)并不知道。你想要的是类似Either的类型,它不是monadic,但是 applicative 。有关差异的详细信息,请参阅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会更加清晰。

答案 2 :(得分:4)

正如@TravisBrown告诉你的那样,因为理解并没有真正与错误累积混合。实际上,当您不希望进行细粒度错误控制时,通常会使用它们。

理解是为了在发现的第一个错误上“短路”,这几乎总是你想要的。

您正在做的坏事是使用String来执行异常的流量控制。您应始终使用Either[Exception, Whatever]并使用scala.util.control.NoStackTracescala.util.NonFatal微调记录。

有更好的选择,特别是:

scalaz.EitherTscalaz.ValidationNel

更新 :(这是不完整的,我不知道你想要什么)。您有比匹配更好的选项,例如getOrElserecover

def testPerson(person: Person): Person = {
  val attempt = Try {
    val pn = testPersonName(person.name)
    val a = testAddress(person.address)
    testPhone(person.phone)
  }
  attempt match {
    case Success(person) => //..
    case Failure(exception) => //..
  }
}

答案 3 :(得分:0)

trmm50km_resample = resample(trmm25,trmm50km, method ='bilinear') class : RasterLayer dimensions : 200, 720, 144000 (nrow, ncol, ncell) resolution : 0.5, 0.5 (x, y) extent : -180, 180, -50, 50 (xmin, xmax, ymin, ymax) coord. ref. : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 data source : in memory names : ppt_200204 values : 0, 1.482134 (min, max) trmm100km_resample = resample(trmm25,trmm100km, method ='bilinear') class : RasterLayer dimensions : 100, 360, 36000 (nrow, ncol, ncell) resolution : 1, 1 (x, y) extent : -180, 180, -50, 50 (xmin, xmax, ymin, ymax) coord. ref. : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 data source : in memory names : ppt_200204 values : 0, 1.202185 (min, max) values(trmm50km) == values(trmm50km_resample) true values(trmm100km) == values(trmm100km_resample) false 开始,我们可以partitionMapScala 2.13中的List个,以便根据元素Either的面对元素进行分区。

Either

如果左侧为空,则没有元素// def testName(pn: Name): Either[String, Name] = ??? // def testAddress(a: Address): Either[String, Address] = ??? // def testPhone(p: Phone): Either[String, Phone] = ??? List(testName(Name("name")), testAddress(Address("address")), testPhone(Phone("phone"))) .partitionMap(identity) match { case (Nil, List(name: Name, address: Address, phone: Phone)) => Right(Person(name, address, phone)) case (left, _) => Left(left) } // Either[List[String], Person] = Left(List("wrong name", "wrong phone")) // or // Either[List[String], Person] = Right(Person(Name("name"), Address("address"), Phone("phone"))) ,因此我们可以从Left元素中构建Person

否则,我们返回Right值中的Left List


中间步骤(Left)的详细信息:

partitionMap