我想知道我的方法应该是什么签名,以便我优雅地处理不同类型的失败。
这个问题在某种程度上是我已经在Scala中处理错误的许多问题的总结。你可以在这里找到一些问题:
目前,我理解以下内容:
存储库图层
现在请考虑我有UserRepository
。 UserRepository
存储用户并定义findById
方法。可能发生以下故障:
OutOfMemoryError
)此外,用户可能会丢失,从而导致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,因为它不是特殊的失败。
使用for-comprehensions
我想继续使用for-comprehensions来组合方法调用。
我们不能轻易地将不同的monad混合起来进行理解,所以我想我的所有操作都应该有某种统一的返回类型吗?
我只是想知道,在现实世界的Scala应用程序中,如何在不同类型的故障发生时继续使用for-understanding进行成功。
目前,for-comprehension对我来说很好,使用的服务和存储库都返回Either[Error,Result]
但是所有不同类型的故障都融合在一起,并且处理这些故障变得很糟糕。
您是否定义了不同类型的monad之间的隐式转换,以便能够使用for-comprehensions?
您是否定义了自己的monad来处理失败?
顺便说一下,我很快就会使用异步IO驱动程序。
所以我想我的返回类型可能更复杂:IO[Future[Either[BusinessError,User]]]
任何建议都会受到欢迎,因为我真的不知道该使用什么,而我的应用程序并不花哨:它只是一个API,我应该能够区分可以向客户显示的业务错误方面和技术错误。我试图找到一个优雅而纯粹的解决方案。
答案 0 :(得分:14)
这就是Scalaz的EitherT
monad变换器的用途。一堆IO[Either[E, A]]
等同于EitherT[IO, E, A]
,除了前者必须按顺序处理为多个monad,而后者自动为单个monad,为基monad添加Either
个功能IO
。您也可以使用EitherT[Future, E, A]
向异步操作添加非异常错误处理。
Monad变换器通常是在单for
- 理解和/或monadic操作中混合多个monad的需要的答案。
修改强>
我假设你使用的是Scalaz 7.0.0版。
为了在EitherT
monad之上使用IO
monad变换器,首先需要导入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
的monad堆栈作为单个统一的monad,可以像往常一样在for
中使用。 EitherT
将负责通过整个计算线程化基本monad(在本例中为IO
),同时还按照Either
通常的方式处理错误(默认情况下已经正确偏置,所以你不必经常处理所有常见的.right
垃圾邮件。当您想要执行IO
操作时,您只需使用liftIO
上的IO
实例方法将其提升到组合monad堆栈中。
作为旁注,当以这种方式工作时,EitherT
伴侣对象中的函数可能非常有用。
答案 1 :(得分:5)
@ pthariens-flame的回答很棒,你应该把它用于手头的任务。
我想带一些关于该领域最新发展的背景知识背景,所以这只是一般信息答案。
错误管理基本上是我们开发的#1工作。幸福的道路是幸福和无聊的,并不是用户会抱怨的地方。大多数(全部?)问题在于过程中隐含的影响(特别是I / O)。
管理这些问题的方法之一是遵循通常所说的“纯FP approch”,在这里你在程序的纯/总和不纯/非总部分之间划出一条大红线。这样做时,您可以利用这种可能性来根据其类型干净地处理错误。
最近(18个月?),Scala已经在该领域看到了很多的研究和开发。实际上,我相信Scala今天是所有语言中最令人兴奋和最具破坏性的地方,在这个非常具体的问题上(当然,它可能只是大脑偏向于可用性/最新信息)。
Scalaz8,Monix和cat-effects是快速进化的3个主要贡献者。因此,与这3个项目(会议讨论,博客文章等)相关的任何内容都将帮助您了解正在发生的事情。
因此,为了简化故事,Scalaz8将改变IO模型的方式,以更好地解决错误管理问题。 John DeGoes在这里领导这项工作,他就这个主题提供了一些很好的资源:
文章:
视频:
还有很多正在进行Monix和Cats效应的事情,但我相信这个主题的大部分资源都发生在相应项目的pull请求中。
亚历山德鲁·内德尔库(Alexandru Nedelcu)的讲话给出了一些问题的背景:
Adam Warski的比较:
[编辑]:正如同行所注意到的,域中(r)进化的跨度并不会停留在scala-land中。尝试使效果编码(IO等)更高效,还有很多工作要做。该域中的最新步骤是尝试使用Kleisli Arrows代替monad来最小化JVM上的GC流失。
见:
希望它有所帮助!
更新 :关于reddit的主题有一个长而有趣的主题:“有人可以向我解释IO的好处吗?” https://www.reddit.com/r/scala/comments/8ygjcq/can_someone_explain_to_me_the_benefits_of_io/
John DeGoes的贡献:“Scala Wars:FP-OOP vs FP”http://degoes.net/articles/fpoop-vs-fp