4

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

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

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


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

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

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

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

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

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

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

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

我想这样做是有道理的:

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

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

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

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

4

4 回答 4

8

我没有看到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(其中加法操作只是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!)

再一次,没有导弹。

于 2012-10-22T19:17:25.880 回答
3

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

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

val result = for {
  _ <- insertItem(item1)
  _ <- insertItem(item2)
  _ <- insertItem(item3)
} yield Unit
于 2012-10-22T19:21:42.673 回答
1

如果您想将包含的方法链接在一起,并且仅在is时Option[Error]执行下一步,您可以使用它。OptionNoneorElse

于 2012-10-22T19:05:33.047 回答
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))
于 2012-10-22T17:21:07.407 回答