ADT vs Either vs 异常
ADT vs Either vs Exceptions
因此,当前的实现使用 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
块中退出。
我想避免抛出异常。解决它的一种方法是 returning Either[Error, X]
,另一种方法 ADT
使用 sealed trait
。因此,您可以 return Left(Error)
或 case object NoCertificate extends CertificateResponse
.
之类的 ADT
而不是抛出 Exception
问题是:如果我将当前具有 throw Exception
的方法替换为 Either
或 ADT
,我能否保持现有的 for 循环完好无损?
为了完整起见,下面是我将如何编写我的 Either
和 ADT
:
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
表示退出条件),因为那不是我们在我们的库中使用的库之一堆。抱歉!
谢谢!
正如我在评论中提到的那样,我真的会研究是否有可能提供一些提供 monad 转换器的库(Scalaz
也包括一个),因为这正是它们所针对的用例。如果真的不可能,您唯一的选择是编写自己的 - 意思是,创建一些 class 可以包装您的方法输出,其中包含 map
和 flatMap
方法来执行您想要的操作。这对于 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% 需要这种包装 class!没有办法让 return 类型 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
转换器的原因是因为它们带有 大量 实用方法和逻辑,可以让您的生活变得更加轻松,但是如果这不是一个选择,这不是一个选择。
因此,当前的实现使用 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
块中退出。
我想避免抛出异常。解决它的一种方法是 returning Either[Error, X]
,另一种方法 ADT
使用 sealed trait
。因此,您可以 return Left(Error)
或 case object NoCertificate extends CertificateResponse
.
ADT
而不是抛出 Exception
问题是:如果我将当前具有 throw Exception
的方法替换为 Either
或 ADT
,我能否保持现有的 for 循环完好无损?
为了完整起见,下面是我将如何编写我的 Either
和 ADT
:
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
表示退出条件),因为那不是我们在我们的库中使用的库之一堆。抱歉!
谢谢!
正如我在评论中提到的那样,我真的会研究是否有可能提供一些提供 monad 转换器的库(Scalaz
也包括一个),因为这正是它们所针对的用例。如果真的不可能,您唯一的选择是编写自己的 - 意思是,创建一些 class 可以包装您的方法输出,其中包含 map
和 flatMap
方法来执行您想要的操作。这对于 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% 需要这种包装 class!没有办法让 return 类型 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
转换器的原因是因为它们带有 大量 实用方法和逻辑,可以让您的生活变得更加轻松,但是如果这不是一个选择,这不是一个选择。