如何在 Scala 中组合 ADT?

发布于 2025-01-12 17:33:35 字数 1417 浏览 4 评论 0原文

我的应用程序有两层:域和应用程序。每层都有自己的“错误”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
      ...
  }
}

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

package com.application.person

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

// and a function f :: Either ApplicationError Something

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

sealed trait ApplicationError extends DomainError // compilation error

我可以创建另一个 case 对象 来包装 DomainError:

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

但该解决方案充其量也不是最优的。

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

doSomething :: Either DoSomethingErrors ???

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

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

谢谢

I have two layers in my app: domain and application. Each layer has its own "error" ADT. For instance:

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
      ...
  }
}

and in my application layer (another package):

package com.application.person

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

// and a function f :: Either ApplicationError Something

The issue is, since DomainError lives in another package, I can't just simply extend my ApplicationError trait:

sealed trait ApplicationError extends DomainError // compilation error

I could create yet another case object to wrap DomainError:

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

but that solution is suboptimal at best.

And also, what if I want to be more specific in my doSomething() and, instead of returning a whole DomainError, a different subset?

doSomething :: Either DoSomethingErrors ???

I would have to account for all cases in each of my domain layer's functions.

Is there any way I can do a proper sum type in Scala?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

蓝天白云 2025-01-19 17:33:35

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[???]
  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[???]
  1. 将 DomainErrors 解释为 ApplicationErrors
val maybeDomainErrorVal: Either[DomainError, ???] = ???
val maybeApplicationErrorVal: Either[ApplicationError, ???] = 
  maybeDomainErrorVal.leftMap {
    case NoPermission => UnexpectedFatalError
  }

Wrapping your domain error in application error is not a bad idea, TBH. It's what I would've done in your situation. A few more options to consider:

  1. make your DomainError and ApplicationError extends a common supertype Error, CommonError, Failure, etc. My personal preference is to extend Throwable - this way your error ASTs can become isomorphic to exceptions which can come in handy for Java interop reasons.

  2. error channel also being composed of unions. Your final type will look somewhat like Either[ApplicationError Either DomainError, A]. It's a bit mouthful but you can make it look less ugly by introducing aliases.

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

def doSomething: Result[???]
  1. replace either with your own AST or use scalaz or other library's alternatives to 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[???]
  1. Interpret DomainErrors into ApplicationErrors
val maybeDomainErrorVal: Either[DomainError, ???] = ???
val maybeApplicationErrorVal: Either[ApplicationError, ???] = 
  maybeDomainErrorVal.leftMap {
    case NoPermission => UnexpectedFatalError
  }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文