我已经发布了很多关于Scala中失败处理的问题,我非常感谢大家的答案。
我在处理Either和Scalaz或理解时理解我的选择,而我还有另一个(最后?)问题:
当操作处理外部非功能性世界(如DB)时,如何执行快速失败的操作序列?
我的意思是我有这样的方法:
def insertItem(item: Item): Either[Error,Item]
感谢Either和这些答案,我知道如何使用:Chaining method calls with Either和 Method 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会处理这样的事情,因为它似乎更专注于真正的函数式编程,也许不提供像我的用例这样的副作用行为的代码?
答案 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]
的方法链接在一起,并且仅在Option
为None
时执行下一步,则可以使用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))