【问题标题】:Handling fail-fast failures when the return type is Option[Error]返回类型为 Option[Error] 时处理快速失败的故障
【发布时间】:2012-10-22 16:12:52
【问题描述】:

我已经发布了很多关于 Scala 中的故障处理的问题,非常感谢大家的回答。

在处理 Either 和 Scalaz 或 for 理解时,我了解我的选择,但我还有另一个(最后一个?)问题:

当操作处理外部非功能性世界(如数据库)时,如何执行快速失败的操作序列?


我的意思是我有这样的方法:

def insertItem(item: Item): Either[Error,Item]

感谢 Either 和这些答案,我知道如何使用 Either:Chaining method calls with EitherMethod parameters validation in Scala, with for comprehension and monads

但我的 Item 案例类是不可变的,将它作为 Right 返回并没有任何意义,因为调用者已经拥有该值。

因此,我怎样才能做同样的事情:

def insertItem(item: Item): Option[Error]

在我的应用程序中,当创建用户时,我们还为他创建了一些项目。 如果一个项目创建失败,那么整个过程应该停止。

当我在 for 理解中直接使用 Option[Error] 时,我认为我不会得到我期望的结果。

我想这样做是有道理的:

for {
  _ <- insertItem(item1).toLeft("???").right
  _ <- insertItem(item2).toLeft("???").right
  _ <- insertItem(item3).toLeft("???").right
}

但是作为值“???”我输入的权利永远不会有用,我想我错过了不涉及创建永远不会使用的权利的优雅解决方案。

我想我正在寻找仅当结果为 None 时才会继续 for 理解的东西,这有点奇怪,因为我只想继续下一个操作,而不是真正的 map操作。

顺便说一句,如果可能的话,我想要非 Scalaz 和 Scalaz 解决方案。 我不确定 Scalaz 是否能处理这些事情,因为它似乎更专注于真正的函数式编程,并且可能不为像我的用例那样的副作用行为提供代码?

【问题讨论】:

    标签: scala scalaz


    【解决方案1】:

    我没有看到在这种情况下不使用Either[Error, Unit] 的原则性理由(或者至少我已经这样做了,我对此并不感到内疚)。假设我们有以下内容:

    def launch(thing: String): Either[String, Unit] = Either.cond(
      thing.nonEmpty,
      println("Launching the " + thing),
      "Need something to launch!"
    )
    

    我们可以证明正确的投影单子是适当的惰性:

    scala> for {
         |   _ <- launch("flowers").right
         |   _ <- launch("").right
         |   r <- launch("MISSILES").right
         | } yield r
    Launching the flowers
    res1: Either[String,Unit] = Left(Need something to launch!)
    

    没有导弹按照需要发射。


    值得注意的是,如果您使用Option 而不是Either,您所描述的操作只是给定Option 的“First”幺半群实例的总和(其中加法操作只是orElse )。例如,我们可以使用 Scalaz 7 编写以下代码:

    import scalaz._, Scalaz._
    
    def fst[A](x: Option[A]): Option[A] @@ Tags.First = Tag(x)
    
    def launch(thing: String): Option[String] = if (thing.isEmpty) Some(
      "Need something to launch!"
    ) else {
      println("Launching the " + thing)
      None
    }
    

    现在:

    scala> val r: Option[String] = fst(launch("flowers")) |+| fst(
         |   launch("")) |+| fst(launch("MISSILES"))
    Launching the flowers
    r: Option[String] = Some(Need something to launch!)
    

    再一次,没有导弹。

    【讨论】:

      【解决方案2】:

      我们在使用 scalaz 时会做类似的事情,以快速发送消息失败,尽管这不是惯用的 scalaz:

      def insertItem(item: Item): Validation[Error, Unit]
      
      val result = for {
        _ <- insertItem(item1)
        _ <- insertItem(item2)
        _ <- insertItem(item3)
      } yield Unit
      

      【讨论】:

      • 我认为 Scalaz 验证是一个错误列表,而不仅仅是一个错误,而且它不是快速失败的,对吧?
      • @SebastienLorber Validation 的 flatMap 方法是快速失败的。 Applicative 实例累积。
      • 还有 Scalaz Either 类型 (\/),它只是 scala.Either 的更合理版本。
      • @Huw 你能提供一个如何在这种情况下使用它的例子吗? \/ 在 Google 中很难搜索
      • def insertItem(item: Item): Error \/ Unit; val result = for { _ &lt;- insertItem(item1); _ &lt;- insertItem(item2); _ &lt;- insertItem(item3) } yield Unit.
      【解决方案3】:

      如果要将包含Option[Error] 的方法链接在一起,并且仅在OptionNone 时执行下一步,则可以使用orElse

      【讨论】:

      • 这是一个好主意,但我没想到。它适用于以下操作的惰性评估。唯一的问题是奇怪的可读性
      【解决方案4】:

      如果您只对第一次失败感兴趣,那么Iterator 可以是您的选择:

      case class Item(i: Int)
      
      case class Error(i: Item)
      
      val items = Iterator(1,3,7,-5,-2,0) map Item
      
      def insertItem(item: Item): Option[Error] =
        if (item.i < 0) Some(Error(item)) else None
      
      scala> val error = (items map insertItem find (_.isDefined)).flatten
      error: Option[Error] = Some(Error(Item(-5)))
      

      如果insertItem 不是您唯一的方法,则可以将它们与您的值分开:

      val items = Iterator(
        () => insertItem(i1),
        () => deleteItem(i2),
        () => insertItem(i3),
        () => updateItem(i4))
      

      【讨论】:

        猜你喜欢
        • 2011-05-19
        • 1970-01-01
        • 2015-12-17
        • 2016-04-21
        • 2019-12-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多