簡體   English   中英

如何在兩者中累積錯誤?

[英]How to accumulate errors in 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] = ...

現在我定義了一個新的案例類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*函數並返回testPerson Either[List[String], Person] 我怎樣才能做到這一點 ?

您想隔離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)      
}

Scala 的for -comprehensions(對flatMapmap調用的組合進行脫糖)旨在允許您以這樣的方式for 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)步驟。

在您的情況下,您的計算沒有這種依賴性,但是您使用的抽象(單子排序)不知道這一點。 你想要的是一個類似Either的類型,它不是monadic,但它是applicative 有關差異的一些詳細信息,請參閱我的答案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會更簡潔一些。

正如@TravisBrown 告訴你的那樣,理解並沒有真正與錯誤累積混合在一起。 事實上,當您不想要細粒度的錯誤控制時,通常會使用它們。

A for comprehension 會在發現第一個錯誤時“短路”自己,這幾乎總是您想要的。

您正在做的壞事是使用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) => //..
  }
}

Scala 2.13開始,我們可以partitionMap一個List of 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 ,因此我們可以用Right元素構建一個Person

否則,我們返回Left值的Left List


中間步驟( partitionMap )的詳細信息:

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")))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM