返回类型为Option [Error]时处理失败快速失败

时间:2012-10-22 16:12:53

标签: scala scalaz

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

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

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


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

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

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

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

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

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

在我的应用程序中,创建用户时,我们还为他创建了一些项目。 如果某个项目无法创建,则整个过程应该停止。

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

我认为这样做是有意义的:

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

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

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

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

4 个答案:

答案 0 :(得分:8)

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

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

我们可以证明正确的投影monad是适当的懒惰:

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”monoid实例的总和(其中操作只是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!)

再次,没有导弹。

答案 1 :(得分:3)

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

def insertItem(item: Item): Validation[Error, Unit]

val result = for {
  _ <- insertItem(item1)
  _ <- insertItem(item2)
  _ <- insertItem(item3)
} yield Unit

答案 2 :(得分:1)

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

答案 3 :(得分:0)

如果您只对第一次失败感兴趣,可以选择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))