Ral*_*lph 8 scala nested option scala-cats
说我有三个数据库访问功能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))
}
}
}
Run Code Online (Sandbox Code Playgroud)
可以看出,代码严重嵌套.
相反,我使用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"))
Run Code Online (Sandbox Code Playgroud)
如果我使用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)
Run Code Online (Sandbox Code Playgroud)
这仍然有点笨拙,但它具有成为标准库的一部分的优势.
例外的是另一种方式去,但他们慢下来一个不少,如果失败的病例屡见不鲜.如果失败真是异常,我只会使用它.
也可以使用非本地回报,但这种特殊设置有点尴尬.我认为正确的预测Either
是要走的路.如果你真的喜欢这种方式,但不喜欢把它.right
放在一个地方,你可以找到一个"偏右的Either"的地方,默认情况下就像正确的投影一样(例如ScalaUtils,Scalaz等).
我想出了这个解决方案(基于@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
Run Code Online (Sandbox Code Playgroud)
他们执行以下操作:
ifTrue
当函数返回 a 时使用Boolean
,表示true
“成功”情况(例如:isAllowed(userId)
)。它实际上会返回,因此应该在理解中Unit
使用。_ <- ifTrue(...) { error }
for
none
当函数返回“成功”情况时使用(例如:Option
用于创建具有唯一电子邮件地址的帐户)。它实际上会返回,因此应该在理解中使用。None
findUser(email)
Unit
_ <- none(...) { error }
for
some
当函数返回 anOption
且Some()
为“成功”情况时使用(例如:findUser(userId)
对于 a 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)
}
Run Code Online (Sandbox Code Playgroud)
这会返回一个Either[String, X]
,其中String
是错误消息,X
是调用的结果process
。