【问题标题】:ADT vs Either vs ExceptionsADT vs 任意 vs 异常
【发布时间】:2019-08-15 07:55:29
【问题描述】:

因此,当前的实现使用 twitter 的 Future 以及抛出异常来表示无效用例以及 for-comprehensions,如下所示:

def someMethod(a: ...): Future[X] = {
  // do something
  // if something goes wrong throw exception
  throw new Exception("Certificate not issued")
}

// in some other method, where exceptions break the loop and exit
def someOtherMethod(a: ...): Future[Y] = {
  for {
    x <- someMethod(...)
    y <- yetAnotherMethod(...) // which throws another exception
  } yield y
}

一般的想法是,当出现问题时,会引发异常,这将导致退出for-comprehension 块。 我想避免抛出异常。解决它的一种方法是返回Either[Error, X],另一种方法是使用sealed trait 返回ADT。因此,您可以返回Left(Error)ADT,而不是抛出Exception,例如case object NoCertificate extends CertificateResponse

问题是:如果我用EitherADT 替换当前具有throw Exception 的方法,我能否保持现有for 循环不变?

为了完整起见,下面是我的 EitherADT 编码方式:

sealed trait Error
case object CertificateError extends Error
case object SomeOtherError extends Error

def someMethod(a: ...): Future[Either[Error, CertificateResponse]] = {
  // returns Left(CertificateError) or Right(CertificateResponse)
}

sealed trait CertificateResponse
case class Certificate(x509) extends CertificateResponse
case object NoCertificate extends CertificateResponse

def someMethod(a: ...): Future[CertificateResponse] = {
  // returns NoCertificate or Certificate(X509)
}

这些替代解决方案(抛出异常和破坏引用透明度)中的任何一个都可以与for-comprehensions 一起使用吗?否定响应:Left()NoCertificate 会自动退出for-comprehension 块吗?如果没有,如何制作,以便我可以保持 for-comprehension 块原样?类似于cats EitherT's leftMap 的东西?

请注意:我们不能像EitherT 那样使用cats Monad Transformer(它有leftMap 表示退出条件),因为这不是我们在堆栈中使用的库之一。对不起!

谢谢!

【问题讨论】:

  • Scalaz 还包含一个EitherT,该库是一个选项吗?因为实际上,您指定的行为是准确地使用 monad 转换器得到的,如果您不能使用库,那么您将不得不手动编写代码。

标签: scala adt for-comprehension either


【解决方案1】:

正如我在评论中提到的,我真的会研究是否有可能提供一些提供 monad 转换器的库(Scalaz 也包括一个),因为这正是它们的用例。如果真的不可能,你唯一的选择是编写你自己的 - 意思是,创建一些可以包装你的方法输出的类,这些方法输出有 mapflatMap 方法,可以做你想做的事。这对于 Either 和基于 ADT 的解决方案都是可行的。基于 Either 的看起来有点像这样:

sealed trait Error

case object CertificateError extends Error
case object SomeOtherError extends Error

case class Result[+T](value: Future[Either[Error, T]]) {
  def map[S](f: T => S)(implicit ec: ExecutionContext) : Result[S] = {
    Result(value.map(_.map(f)))
  }

  def flatMap[S](f: T => Result[S])(implicit ec: ExecutionContext) : Result[S] = {
    Result {
      value.flatMap {
        case Left(error) => Future.successful(Left(error))
        case Right(result) => f(result).value
      }
    }
  }
}

(你 100% 需要这种包装类!没有办法让返回类型 Future[ADT]Future[Either[Error, Result]] 以你想要的方式运行,因为它需要改变 Future 的工作方式。)

使用上面的代码,您可以在Result 类型上使用for-comprehensions,如果它们包含Future 失败或Future 成功并带有您指定的Error,它们将自动退出.愚蠢的例子:

import ExecutionContext.Implicits.global
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._

def getZero() : Result[Int] = Result(Future.successful(Right(0)))

def error() : Result[Unit] = Result(Future.successful(Left(SomeOtherError)))

def addTwo(int: Int) : Result[Int] = Result(Future.successful(Right(2 + int)))

val result = for {
  zero <- getZero()
  _ <- error()
  two <- addTwo(zero)
} yield two

Await.result(result.value, 10.seconds) // will be `Left(SomeOtherError)`

我强烈推荐现有的 EitherT 转换器的原因是因为它们带有 大量 实用方法和逻辑,可以让你的生活变得更轻松,但如果它不是一个选项,那就不是一个选项。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-11
    • 1970-01-01
    • 2011-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多