尝试[结果],IO [结果],应该使用[错误,结果],我应该在最后使用
我想知道我的方法的签名应该如何,以便优雅地处理不同类型的故障。尝试[结果],IO [结果],应该使用[错误,结果],我应该在最后使用
这个问题在某种程度上是我已经有很多关于Scala错误处理的问题的总结。你可以在这里找到一些问题:
- Throwing exceptions in Scala, what is the "official rule"
- Either, Options and for comprehensions
- Either monadic operations
现在,我的理解如下:
- 要么可以作为结果包装的方法调用,失败
- 尝试是biaised无论是权上的失败是一个非致命的异常
- IO(scalaz)有助于建立一个处理IO操作的纯方法
- 所有3容易在为理解
- 的全部可用3不容易混合的一个,因为不兼容flatMap方法
- 在功能性语言研究,我们通常不会抛出异常,除非他们是致命的
- 我们应该抛出异常的真正特殊的理解条件。我想这是尝试
- 创建将Throwable先后为JVM性能为代价的做法,它不打算用于业务流程CONTROLE
库层现在
请考虑我有一个UserRepository
。 UserRepository
存储用户并定义findById
方法。以下故障可能发生:
- 致命故障(
OutOfMemoryError
) - 一个IO失败,因为数据库不存取/可读
此外,用户可能会丢失,从而导致Option[User]
结果
使用存储库的JDBC实现,可以抛出SQL,非致命异常(约束违规或其他),因此使用Try可能有意义。
当我们正在处理IO操作时,如果我们需要纯函数,IO monad也是有意义的。
所以结果类型可能是:
Try[Option[User]]
IO[Option[User]]
- 别的东西吗?
服务层
现在介绍一个业务层,UserService
,它提供了一些方法updateUserName(id,newUserName)
使用先前定义的存储库findById
。
以下故障可能发生:
- 传播到服务层的所有库失败
- 业务错误:无法更新的用户不存在
- 业务错误的用户名:新用户名太短
那么结果类型可能是:
Try[Either[BusinessError,User]]
IO[Either[BusinessError,User]]
- 别的东西吗?
这里的BusinessError不是Throwable,因为它不是一个特殊的失败。
使用换内涵
我想使用-推导方法调用相结合,以保持。
我们不能轻易地将不同的monads混合在一个for-comprehension中,所以我想我应该对我所有的操作都有某种统一的返回类型吗?
我只是想知道,在现实世界的Scala应用程序中,如何在不同种类的故障可能发生时继续使用for-comprehensions,取得成功。
现在,换修真我工作得很好,用服务和信息库,所有返回Either[Error,Result]
但所有不同类型的故障被熔化在一起,成为一种哈克来处理这些故障。
您是否定义了不同种类的monads之间的隐式转换以便能够使用for-comprehensions?
您是否定义了自己的monads来处理故障?
顺便说一下,也许我会尽快使用异步IO驱动程序。 所以我想我的返回类型可能是更加复杂:IO[Future[Either[BusinessError,User]]]
任何意见将是受欢迎的,因为我真的不知道干什么用的,而我的应用程序并不花哨:它只是一个API,其中我应该能够区分可以向客户端显示的业务错误和技术错误。我试图找到一个优雅和纯粹的解决方案。
这是Scalaz的EitherT
monad变压器的用途。 IO[Either[E, A]]
的堆栈相当于EitherT[IO, E, A]
,不同之处在于前者必须按顺序处理为多个单元,而后者自动为单个单元,其将Either
功能添加到基本单元IO
。您也可以使用EitherT[Future, E, A]
将异常错误处理添加到异步操作。
Monad变压器通常是需要在单个for
- 综合和/或单子操作中混合多个单元的需求的答案。
编辑:
我会假设你使用Scalaz 7.0.0版。
为了使用EitherT
单子变压器在IO
单子的顶部,您首先需要导入Scalaz的相关部分:
import scalaz._, scalaz.effect._
您还需要定义你的错误类型:RepositoryError
,BusinessError
,等等。这照常工作。您只需确保您可以将任何RepositoryError
转换为BusinessError
,然后进行模式匹配以恢复确切的错误类型。
然后你的方法的签名变成了:
def findById(id: ID): EitherT[IO, RepositoryError, User]
def updateUserName(id: ID, newUserName: String): EitherT[IO, BusinessError, User]
在每个你的方法,你可以使用EitherT
- 和 - IO
基于单子堆栈一个统一的单子,在for
-comprehensions可用照常。 EitherT
将负责在整个计算中对基本monad进行线程化处理(在本例中为IO
),同时还通过Either
通常的方式处理错误(除了默认情况下已经是右偏),因此您不必经常处理所有通常为.right
垃圾)。当您想要执行IO
操作时,只需使用liftIO
实例方法IO
将其提升到组合monad堆栈中即可。
请注意,以这种方式工作时,EitherT
伴侣对象中的功能可能非常有用。
谢谢,这似乎正是我需要的。不幸的是,我还没有斯卡拉兹的技能,以了解它是如何工作的:(你能提供一个简单的例子,说明如何使用它们,使用我给出的方法签名? – 2013-05-01 21:17:19
@SebastienLorber我编辑了我的答案,尝试更好地解释。 – 2013-05-01 23:00:14
@SebastienLorber斯卡拉兹提示。为了更好地理解它们的工作方式,尝试自行重新实现一些东西是一个好主意。在这种情况下,为'EitherT'实现Monad实例是一个很好的练习。 – Eric 2013-05-02 11:53:15
能否请你发表一些代码,你最终如何写这个?我非常感兴趣,因为我有一个类似的模式。我的斯卡拉技能并不是我希望他们成为的......我很难理解这些课程。 – costa 2017-08-21 08:11:10