尝试[结果],IO [结果],[错误,结果],我应该在最后使用

时间:2013-05-01 13:03:03

标签: scala scalaz either

我想知道我的方法应该是什么签名,以便我优雅地处理不同类型的失败。

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


目前,我理解以下内容:

  • 任何一个都可以用作可能失败的方法调用的结果包装器
  • 尝试是一个正确的biaised要么失败是一个非致命的例外
  • IO(scalaz)有助于构建处理IO操作的纯方法
  • 所有3个都很容易用于理解
  • 因为不兼容的flatMap方法,所有3个都不容易混合以便理解
  • 在功能性语言中,我们通常不会抛出异常,除非它们是致命的
  • 我们应该为真正特殊情况抛出异常。我想这是尝试
  • 的方法
  • 创建Throwables具有JVM的性能成本,并不打算用于业务流程控制

存储库图层

现在请考虑我有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,因为它不是特殊的失败。


使用for-comprehensions

我想继续使用for-comprehensions来组合方法调用。

我们不能轻易地将不同的monad混合起来进行理解,所以我想我的所有操作都应该有某种统一的返回类型吗?

我只是想知道,在现实世界的Scala应用程序中,如何在不同类型的故障发生时继续使用for-understanding进行成功。

目前,for-comprehension对我来说很好,使用的服务和存储库都返回Either[Error,Result]但是所有不同类型的故障都融合在一起,并且处理这些故障变得很糟糕。

您是否定义了不同类型的monad之间的隐式转换,以便能够使用for-comprehensions?

您是否定义了自己的monad来处理失败?

顺便说一下,我很快就会使用异步IO驱动程序。 所以我想我的返回类型可能更复杂:IO[Future[Either[BusinessError,User]]]


任何建议都会受到欢迎,因为我真的不知道该使用什么,而我的应用程序并不花哨:它只是一个API,我应该能够区分可以向客户显示的业务错误方面和技术错误。我试图找到一个优雅而纯粹的解决方案。

2 个答案:

答案 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._

您还需要定义错误类型:RepositoryErrorBusinessError等。这样可以正常工作。您只需确保可以,例如,将任何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的比较:

最后,卢卡·雅各布维茨(Luka Jacobowitz)为“猫”(Cats)部分撰写了一篇精彩的文章:“重新思考MonadError”https://typelevel.org/blog/2018/04/13/rethinking-monaderror.html,它涵盖了许多同样的理由和其他光。

[编辑]:正如同行所注意到的,域中(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