scala - 通过任一/析取 Scala 处理多种错误类型

标签 scala error-handling functional-programming scalaz either

我有 3 个不同的模块,每个模块都有自己的错误类型。以下是一个非常简化的版本。

object ModuleA {
  case class ErrorA(msg: String)
  def getA: ErrorA \/ String = "1".right
}

object ModuleB {
  case class ErrorB(msg: String)
  def getB(s: String): ErrorB \/ Int = 1.right
}

object ModuleC {
  case class ErrorC(msg: String)
  def getC(s: String, i: Int): ErrorC \/ Long = 1L.right
}

作为这些模块的客户,链接这些调用的最佳方式是什么。

首先 - 深度嵌套的复杂返回类型,但具有所需的所有类型。
def call1: ModuleA.ErrorA \/ (ModuleB.ErrorB \/ (ModuleC.ErrorC \/ Long)) = {
  ModuleA.getA.map { s =>
    ModuleB.getB(s).map { i =>
      ModuleC.getC(s, i)
    }
  }
}

第二 - 非常易读,但错误类型丢失(推断返回类型为 Product \/ Long )。理想情况下,需要与错误类型类似的东西
def call2  =
  for {
    s  <- ModuleA.getA
    i  <- ModuleB.getB(s)
    l  <- ModuleC.getC(s, i)
  } yield l

第三 - 定义新的错误类型来封装现有的。这对于不同的组合似乎是不可行的

最后,尝试使用 EitherT,但似乎变得复杂

最佳答案

考虑从错误中创建代数数据类型,用于 example

sealed abstract class Error(val message: String)
case class ErrorA(msg: String) extends Error(msg)
case class ErrorB(msg: String) extends Error(msg)
case class ErrorC(msg: String) extends Error(msg)

然后更改返回的\/的左侧至Error
import scalaz.\/
import scalaz.syntax.either._

object ModuleA {
  def getA: Error \/ String = "1".right
}

object ModuleB {
  def getB(s: String): Error \/ Int = ErrorB("boom").left
}

object ModuleC {
  def getC(s: String, i: Int): Error \/ Long = 1L.right
}

for {
  s  <- ModuleA.getA
  i  <- ModuleB.getB(s)
  l  <- ModuleC.getC(s, i)
} yield l

这使
res0: Error \/ Long = -\/(ErrorB(boom))

如果您无法创建 ADT,请考虑 leftMap将错误类型更改为像这样的常见类型
case class ErrorWrapper(m: String)

for {
  s  <- ModuleA.getA.leftMap { e: ModuleA.ErrorA => ErrorWrapper(e.msg) }
  i  <- ModuleB.getB(s).leftMap { e: ModuleB.ErrorB => ErrorWrapper(e.msg) }
  l  <- ModuleC.getC(s, i).leftMap { e: ModuleC.ErrorC => ErrorWrapper(e.msg) }
} yield l
// res0: ErrorWrapper \/ Long = -\/(ErrorWrapper(boom))

或者甚至,不寻常地,通过结构类型
implicit class CommonErrorWrapper[A <: Product { def msg: String }](e: A) {
  def toErrorWrapper: ErrorWrapper = ErrorWrapper(e.msg)
}

for {
  s  <- ModuleA.getA.leftMap(_.toErrorWrapper)
  i  <- ModuleB.getB(s).leftMap(_.toErrorWrapper)
  l  <- ModuleC.getC(s, i).leftMap(_.toErrorWrapper)
} yield l
// res1: ErrorWrapper \/ Long = -\/(ErrorWrapper(boom))
leftMap不仅对于更改错误类型很有用,而且我们可以通过添加本地可用的上下文信息来丰富错误。

备注 EitherT当类型的形状为 F[A \/ B] 时,可以使用单子(monad)更改器(mutator)。 ,例如 Future[Error \/ B] ,但是在您的情况下,它只是 A \/ B ,因此 EitherT这可能不是正确的工具。相关问题 EitherT with multiple return types

关于scala - 通过任一/析取 Scala 处理多种错误类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60313644/

相关文章:

node.js - 默认情况下 NodeJS 加密和二进制编码

multithreading - Spark : driver logs showing “thread spilling sort data to disk”

python-3.x - 错误处理 : Not excepting syntax error

matlab - MATLAB中的逐次函数应用

scala - 找出两个变量是否从Scala中的同一参数化类型继承

scala - 如何修改数组中的每个值?

error-handling - Controller 中的错误处理

ruby-on-rails - Rails 401 身份验证错误的自定义处理程序

java - 根据条件(Java/Guava)从列表中提取第一个匹配项?

java - 如何使用流将此程序转换为 Java 8 函数式风格?