Scala 增量更新对象字段

Scala update object fields incrementally

我需要建立包含多个字段的记录。

记录定义如下:

class Record(
  f1: Int,
  f2: String,
  ...
  err: String
)

逻辑是这样的:

record.f1 = get_f1()
record.f2 = get_f2()
...

对get_f1等的调用可能会抛出异常。发生这种情况时,我们将 record.err 设置为异常消息。

现在,我的 hacky 解决方案是使用可变 Record 实例并逐步修补字段。当异常发生时,我将从外部函数中捕获它并设置错误字段。

问题是如何以更实用的方式实现它?

------编辑-----

我们需要err字段来输出第一个异常 当异常发生时,我们仍然希望输出已经填充的字段

这是一种(主要)实用的方法:

import scala.util.{Try, Success, Failure}

Try {
  Record(getf1(), getf2(), "")
} match {
  case Success(r) => r
  case Failure(e) => Record(0, "", e.getMessage)
}

如果 Try 中的代码运行正常,那么它将 return Success 并且 match 将提取值并 return 它。

如果代码抛出异常,那么它将被 Try 捕获,并且它将 return Failurematch 将捕获错误代码,然后创建一个 Return 的值,并将 err 设置为来自异常的错误消息。


正如其他人所提到的,Either 类型是处理错误情况的更好方法,因为它从 Record class 本身中删除了错误信息:

Try {
  Record(getf1(), getf2())
}.toEither

成功时结果为 Right(<record>),错误时为 Left(<exception>)。您可以使用 match 来确定给出的答案。


对于修改后的问题,您需要使用 Try 调用的嵌套链,记录中的每个字段都嵌套一层。

Try(getf1()) match {
  case Failure(e) => Record(0, "", e.getMessage)
  case Success(f1) =>
    Try(getf2()) match {
      case Failure(e)  => Record(f1, "", e.getMessage)
      case Success(f2) => Record(f1, f2, "")
    }
}

这个相当曲折的代码是问题中混合错误处理方法的结果。更标准的设计会对整个记录(如上例所示)或每个单独的字段(通过使每个字段成为 OptionEither 值)进行错误处理。

The question is how can I implement this in a more functional way?

通过以功能样式正确建模。

首先,让我们从 case class 中删除 err 字段,而是使用 Either
其次,让我们使用 Option
正确处理 nulls 最后,让我们使用像 cats.

这样的函数库

最终结果会是这样的:

import cats.data.EitherNec
import cats.syntax.all._

type Error = String // You may use a better data type.

final case class Record(
  f1: Int,
  f2: String,
  f3: Boolean
)

object Record {
  def fromRawData(f1: Option[Int], f2: Option[String], f3: Option[Boolean]): EitherNec[Error, Record] =
    (
      f1.toRightNec(left = "F1 was empty"),
      f2.toRightNec(left = "F2 was empty"),
      f3.toRightNec(left = "F3 was empty")
    ).parMapN(Record.apply)
}

The EitherNec[E, A] is just an alias for Either[NonEmptyChain[E], A]
Meaning that if it is a Left it will hold all the errors; it also implies that there must be at least one error.

如有任何 follow-up 问题,请随时提出。


如果您不想使用 cats,您可以将 parMapN 替换为 for;但这将在第一次失败时停止。


可以看到代码运行 here.