假设我有三个数据库访问函数 foo
, bar
, 和 baz
可以各自返回Option[A]
哪里A
是一些模型类,调用相互依赖。
我想按顺序调用这些函数,并且在每种情况下,如果找不到值( None
),则返回适当的错误消息。
我当前的代码如下所示:
Input is a URL: /x/:xID/y/:yID/z/:zID
foo(xID) match {
case None => Left(s"$xID is not a valid id")
case Some(x) =>
bar(yID) match {
case None => Left(s"$yID is not a valid id")
case Some(y) =>
baz(zID) match {
case None => Left(s"$zID is not a valid id")
case Some(z) => Right(process(x, y, z))
}
}
}
可以看出,代码嵌套严重。
如果相反,我使用
for
理解,我不能给出具体的错误信息,因为我不知道哪一步失败了:(for {
x <- foo(xID)
y <- bar(yID)
z <- baz(zID)
} yield {
Right(process(x, y, z))
}).getOrElse(Left("One of the IDs was invalid, but we do not know which one"))
如果我使用
map
和 getOrElse
,我最终得到的代码几乎与第一个示例一样嵌套。这些是否有更好的方法来构建它以避免嵌套,同时允许特定的错误消息?
最佳答案
您可以获取您的 for
通过使用正确的投影循环工作。
def ckErr[A](id: String, f: String => Option[A]) = (f(id) match {
case None => Left(s"$id is not a valid id")
case Some(a) => Right(a)
}).right
for {
x <- ckErr(xID, foo)
y <- ckErr(yID, bar)
z <- ckErr(zID, baz)
} yield process(x,y,z)
这仍然有点笨拙,但它具有成为标准库的一部分的优势。
异常是另一种方法,但如果失败案例很常见,它们会大大减慢速度。如果失败真的很特别,我只会使用它。
也可以使用非本地返回,但对于这种特定设置来说有点尴尬。我认为
Either
的正确预测是要走的路。如果您真的喜欢这种方式工作但不喜欢放置 .right
到处都是,你可以在很多地方找到一个“偏右的任一个”,默认情况下它会像正确的投影一样(例如 ScalaUtils ,Scalaz 等)。
关于scala - 在 Scala 中避免深度嵌套的 Option 级联,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28884742/