假设我有三个数据库访问函数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
,我的代码与第一个示例几乎一样嵌套。
这是一些更好的方式来构造这个结构,以避免嵌套,同时允许特定的错误消息?
发布于 2015-03-06 12:07:54
我想出了这个解决方案(基于@Rex的解决方案和他的评论):
def ifTrue[A](boolean: Boolean)(isFalse: => A): RightProjection[A, Unit.type] =
Either.cond(boolean, Unit, isFalse).right
def none[A](option: Option[_])(isSome: => A): RightProjection[A, Unit.type] =
Either.cond(option.isEmpty, Unit, isSome).right
def some[A, B](option: Option[A])(ifNone: => B): RightProjection[B, A] =
option.toRight(ifNone).right
它们所做的工作如下:
ifTrue
在函数返回Boolean
时使用,true
是“成功”的例子(例如:isAllowed(userId)
)。它实际上返回Unit
,因此在for
理解中应该用作_ <- ifTrue(...) { error }
。none
为“成功”的情况(例如:用于创建具有唯一电子邮件地址的帐户的findUser(email)
)的Option
时,会使用None
。它实际上返回Unit
,因此在for
理解中应该用作_ <- none(...) { error }
。some
时,使用Some()
作为“成功”的例子(例如:findUser(userId)
表示GET /users/userId
)。它返回Some
:user <- some(findUser(userId)) { s"user $userId not found" }
的内容。它们用于for
理解:
for {
x <- some(foo(xID)) { s"$xID is not a valid id" }
y <- some(bar(yID)) { s"$yID is not a valid id" }
z <- some(baz(zID)) { s"$zID is not a valid id" }
} yield {
process(x, y, z)
}
这将返回一个Either[String, X]
,其中String
是一个错误消息,X
是调用process
的结果。
发布于 2015-03-05 11:23:22
通过使用正确的投影,您可以让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等)。
发布于 2015-03-05 10:19:53
我不使用Option
,而是使用Try
。这样,你就有了你想要的一元组合,并有能力保留错误。
def myDBAccess(..args..) =
thingThatDoesStuff(args) match{
case Some(x) => Success(x)
case None => Failure(new IdError(args))
}
在上面的假设中,您并不实际控制这些函数,也无法重构它们以给您一个非Option
。如果您这样做了,那么只需替换Try
。
https://stackoverflow.com/questions/28884742
复制