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

Seb*_*ber 15 scala either scalaz

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

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


现在,我理解以下内容:

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

存储库层

现在请考虑我有一个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,因为它不是一个例外的失败.


使用for-comprehensions

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

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

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

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

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

你定义自己的monad来处理失败吗?

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


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

Pth*_*ame 15

这就是Scalaz的EitherTmonad变换器的用途.堆栈IO[Either[E, A]]等价EitherT[IO, E, A],除了前者必须按顺序处理为多个monad,而后者自动为单个monad,Either为基本monad 添加功能IO.您还可以使用EitherT[Future, E, A]向异步操作添加非异常错误处理.

Monad变换器通常是在单一for理解和/或单一操作中混合多个monad的需要的答案.


编辑:

我假设您使用的是Scalaz 7.0.0版.

为了在EitherTmonad上使用monad变换器IO,首先需要导入Scalaz的相关部分:

import scalaz._, scalaz.effect._
Run Code Online (Sandbox Code Playgroud)

您还需要定义你的错误类型: RepositoryError,BusinessError,等这照常工作.你只需要确保你可以,例如,将any转换RepositoryError为a BusinessError然后模式匹配,以恢复确切的错误类型.

然后你的方法的签名变成:

def findById(id: ID): EitherT[IO, RepositoryError, User]
def updateUserName(id: ID, newUserName: String): EitherT[IO, BusinessError, User]
Run Code Online (Sandbox Code Playgroud)

在每个方法中,您可以使用基于EitherT- 和 - IO的monad堆栈作为单个统一的monad,for像往常一样可用于in -rehe态. EitherT将负责IO通过整个计算线程化基本monad(在这种情况下),同时还按照Either通常的方式处理错误(默认情况下已经正确偏置,因此您不必经常处理所有常见的.right垃圾) .当您想要执行IO操作时,您所要做的就是使用liftIO实例方法将其提升到组合的monad堆栈中IO.

作为旁注,当以这种方式工作时,EitherT伴随对象中的函数可能非常有用.

  • @SebastienLorber一个Scalaz小费.尝试自己重新实现一些事情以便更好地理解它们的工作原理是个好主意.在这种情况下,为`EitherT`实现Monad实例是一个非常好的练习. (2认同)

fan*_*f42 6

@ pthariens-flame的回答很好,您应该将其用于手头的任务。

我想带上该领域最新发展的一些背景资料,所以这只是一个一般性的信息答案。

错误管理基本上是我们开发人员的第一工作。幸福的道路既快乐又无聊,这不是用户会抱怨的地方。大多数(全部?)问题都存在于过程中暗示效果(尤其是I / O)的地方。

解决这些问题的方法之一是遵循通常称为“纯FP方法”的方法,在此方法中,您在程序的纯/全部和不纯/非全部部分之间划了一条大的红线。这样做时,您可以利用可能性根据错误的类型来彻底处理错误。

在最近的时间(18个月?)中,Scala 在该领域进行了许多研究和开发。实际上,我相信Scala是当今所有语言中最棘手和最棘手的问题(但当然,这可能只是可用性/最新信息的大脑偏见)。

Scalaz8,Monix和cats-effects是这种快速发展的3个主要贡献者。因此,与这3个项目相关的任何内容(会议讨论,博客文章等)都将帮助您了解正在发生的事情。

因此,为了简短起见,Scalaz8将更改IO的建模方式,以更好地说明错误管理。John DeGoes领导着这项工作,他就该主题提供了一些很好的资源:

文章:

视频:

Monix和Cats-effect 也有很多事情要做,但是我相信,关于该主题的大多数资源都发生在相应项目中的请求请求中。

亚历山德鲁·内德尔库(Alexandru Nedelcu)的讲话为这些问题提供了一些背景知识:

亚当·沃斯基(Adam Warski)的比较:

最后,Luka Jacobowitz为Cats撰写的精彩文章:“重新思考MonadError” https://typelevel.org/blog/2018/04/13/rethinking-monaderror.html涵盖了很多相同的方面,其他的光。

[编辑]:正如同龄人所注意到的,域中(r)演化的跨度不止于斯卡拉地区。为了使效果编码(除其他外,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)的贡献:“斯卡拉战争:FP-OOP与FP” http://degoes.net/articles/fpoop-vs-fp