如何在 Scala 中组合 ADT?

kib*_*ibe 3 scala scala-cats

我的应用程序有两层:域和应用程序。每层都有自己的“错误”ADT。例如:

package com.domain.person

sealed trait DomainError
case object NoPermission extends DomainError

final case class Person(hasPermission: Boolean): Either[DomainError, ???] {
  def doSomething() = {
    if (!hasPermission)
      Left(NoPermission)
    else
      ...
  }
}
Run Code Online (Sandbox Code Playgroud)

在我的应用程序层(另一个包)中:

package com.application.person

sealed trait ApplicationError
case object PersonNotFound extends ApplicationError
case object UnexpectedFatalError extends ApplicationError

// and a function f :: Either ApplicationError Something
Run Code Online (Sandbox Code Playgroud)

问题是,由于DomainError生活在另一个包中,我不能简单地扩展我的ApplicationError特征:

package com.application.person

sealed trait ApplicationError
case object PersonNotFound extends ApplicationError
case object UnexpectedFatalError extends ApplicationError

// and a function f :: Either ApplicationError Something
Run Code Online (Sandbox Code Playgroud)

我可以创建另一个case object来包装DomainError

sealed trait ApplicationError
// n list of errors, and then:
final case class WrappedDomainError(d: DomainError) extends ApplicationError
Run Code Online (Sandbox Code Playgroud)

但该解决方案充其量只是次优。

另外,如果我想在我的doSomething()and 中更具体,而不是返回整个DomainError,而是返回一个不同的子集,该怎么办?

sealed trait ApplicationError extends DomainError // compilation error
Run Code Online (Sandbox Code Playgroud)

我必须考虑每个域层功能中的所有情况。

有什么方法可以在 Scala 中做正确的求和类型吗?

谢谢

Sim*_*mY4 5

TBH,将域错误包装在应用程序错误中并不是一个坏主意。这就是我在你的情况下会做的事情。还有几个选项需要考虑:

  1. 让你的 DomainError 和 ApplicationError 扩展一个常见的超类型 Error、CommonError、Failure 等。我个人的偏好是扩展 Throwable - 这样你的错误 AST 就可以变得与异常同构,这可以出于 Java 互操作的原因派上用场。

  2. 错误通道也由联合体组成。你的最终类型看起来有点像Either[ApplicationError Either DomainError, A]。它有点拗口,但你可以通过引入别名让它看起来不那么难看。

type Result[+A] = Either[ApplicationError Either DomainError, A]

def doSomething: Result[???]
Run Code Online (Sandbox Code Playgroud)
  1. 用您自己的 AST 替换或使用 scalaz 或其他库的Either3替代品
sealed trait Result[+A]
case class Success[A](a: A) extends Result[A]
case class ApplicationErr(err: ApplicationError) extends Result[Nothing]
case class DomainErr[A](err: DomainErr) extends Result[Nothing]

def doSomething: Result[???]
Run Code Online (Sandbox Code Playgroud)
  1. 将 DomainErrors 解释为 ApplicationErrors
val maybeDomainErrorVal: Either[DomainError, ???] = ???
val maybeApplicationErrorVal: Either[ApplicationError, ???] = 
  maybeDomainErrorVal.leftMap {
    case NoPermission => UnexpectedFatalError
  }
Run Code Online (Sandbox Code Playgroud)