2013-05-01 53 views
10

我想知道我的方法的签名应该如何,以便优雅地处理不同类型的故障。尝试[结果],IO [结果],应该使用[错误,结果],我应该在最后使用

这个问题在某种程度上是我已经有很多关于Scala错误处理的问题的总结。你可以在这里找到一些问题:


现在,我的理解如下:

  • 要么可以作为结果包装的方法调用,失败
  • 尝试是biaised无论是权上的失败是一个非致命的异常
  • IO(scalaz)有助于建立一个处理IO操作的纯方法
  • 所有3容易在为理解
  • 的全部可用3不容易混合的一个,因为不兼容flatMap方法
  • 在功能性语言研究,我们通常不会抛出异常,除非他们是致命的
  • 我们应该抛出异常的真正特殊的理解条件。我想这是尝试
  • 创建将Throwable先后为JVM性能为代价的做法,它不打算用于业务流程CONTROLE

库层现在

请考虑我有一个UserRepositoryUserRepository存储用户并定义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,其中我应该能够区分可以向客户端显示的业务错误和技术错误。我试图找到一个优雅和纯粹的解决方案。

+0

能否请你发表一些代码,你最终如何写这个?我非常感兴趣,因为我有一个类似的模式。我的斯卡拉技能并不是我希望他们成为的......我很难理解这些课程。 – costa 2017-08-21 08:11:10

回答

11

这是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._ 

您还需要定义你的错误类型:RepositoryErrorBusinessError,等等。这照常工作。您只需确保您可以将任何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伴侣对象中的功能可能非常有用。

+1

谢谢,这似乎正是我需要的。不幸的是,我还没有斯卡拉兹的技能,以了解它是如何工作的:(你能提供一个简单的例子,说明如何使用它们,使用我给出的方法签名? – 2013-05-01 21:17:19

+0

@SebastienLorber我编辑了我的答案,尝试更好地解释。 – 2013-05-01 23:00:14

+1

@SebastienLorber斯卡拉兹提示。为了更好地理解它们的工作方式,尝试自行重新实现一些东西是一个好主意。在这种情况下,为'EitherT'实现Monad实例是一个很好的练习。 – Eric 2013-05-02 11:53:15