Scala 如何在这两种情况下累积错误?

Scala 如何在这两种情况下累积错误?,scala,functional-programming,either,Scala,Functional Programming,Either,假设我有几个case类和函数来测试它们: 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): Ei

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

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] = ...
现在我定义了一个新的case类
Person
和一个测试函数,它很快就会失败

案例类人员(姓名:PersonName,地址:address,电话:phone)
def testPerson(person:person):或[String,person]=for{

请注意,您希望隔离
测试*
方法并停止使用理解

假设(出于任何原因)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)      
}

正如@TravisBrown告诉你的,因为理解并不会真正与错误累积混在一起。事实上,当你不想进行细粒度的错误控制时,通常会使用它们

一个需要理解的人在发现第一个错误时就会“短路”,而这几乎总是你想要的

您所做的坏事是使用
String
对异常进行流控制。您应该始终使用
或[Exception,Whatever]
并使用
scala.util.control.NoStackTrace
scala.util.NonFatal
微调日志记录

还有更好的选择,具体来说:

scalaz.EitherT
scalaz.ValidationNel

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

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) => //..
  }
}

斯卡拉的<代码> < /COD> -理解(这是对调用<代码> >平面图< /代码>和<代码> map < /代码>的组合)的设计,使您能够以一种方式对一元计算进行排序,以便在后续步骤中访问较早计算的结果。

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

当然,虽然这比必要的更为冗长,并且会丢弃一些信息,并且在整个过程中使用
验证将更为简洁。

Scala 2.13
开始,我们可以创建
列表,以便根据元素的
一侧对元素进行分区

// 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

否则,我们将返回
值的
列表


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


我知道这不是你想听到的,而是错误积累和理解(或任何一种单元排序)的
不要混用。例如,如果您在调用
testAddress
时使用了
pn
,而
testPersonName
失败了怎么办?请从中查看
ValidationNel
。请参阅。在您的示例中:
def tePersonName(pn:PersonName):ValidationNel[String,PersonName]=…
(testPersonName(person.name)|@|testAddress(person.address)|@|testPhone(person.phone))(person)
。你也可以看看。它允许你以你想要的方式组合
对(person)(testPersonName(person.name)、testAddress(person.address)、testPhone(person.phone))
。免责声明:我没有尝试这个库。同样
也没有[例外,无论什么]
基本上等同于
Try[Watever]
@dmitry-对于“基本”的给定定义@dmitry不一定,您不能总是使用
Try
进行流控制。这两种结构有非常具体的使用场景。@dmitry和@flavian那么,在本例中,我可以使用
Try
而不是
吗?@Michael您可以使您的代码更干净一点,但
Try
仍然没有任何功能lt in专门帮助聚合错误。这可能会更加困难,因为您会发现自己有一个表示多个错误的
异常
:31:error:value validation不是[String,Int]的成员
我应该导入什么?
myEither.disjunction.validation.toValidationNel
很好。哦,对了,
上的
.validation
都是。所以,是的,通过disjunction或使用
验证。Fromor
是7.0中最好的选择。就我所记得的,你应该避免在Sca中捕获
Throwable
la.所以
parseInt
应该捕捉
Exception
case\uuuxception=>…
@r0estir0bbe确实应该是
case NonFatal(e)=>
,但我认为这与这个主题没有太大关系。
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
// 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")))
List(Left("bad name"), Right(Address("addr")), Left("bad phone"))
  .partitionMap(identity)
// (List[String], List[Any]) = (List("bad name", "bad phone"), List[Any](Address("addr")))