嵌套 Scala 选项字段的最佳实践?
Best Practices for nested Scala Option fields?
我有许多嵌套对象,全部包含在 Scala Option 类型中。在我的项目的其他地方,每次调用 .get
时,我都必须调用一个嵌入大约 5 层深的属性(其中一些是列表)。这样我最终得到如下所示的内容:
objectA.get.attrB.get.attrC.get(0).attrD.get
除了一系列 .get
调用(我不确定这是否是理想的)之外,我没有以这种方式实现很多错误处理,如果任何属性为空,那么整个事情就会失败分开。鉴于嵌套调用,如果我将其限制为如上所述的单行,我也只能在最后使用一次 .getOrElse
。
是否有任何在 Scala 中使用选项类型的建议方法?
在这种情况下,开箱即用的最易读的解决方案(即不编写辅助方法)可以说是链式调用 Option.flatMap
:
objectA flatMap (_.attrB) flatMap (_.attrC.headOption) flatMap (_.attrD)
通过使用 flatMap
,如果链中的任何选项是 None
,您最终会得到 None
(与 [=18 不同,没有例外) =] 在 None
).
上被调用时会爆炸
举例:
case class C(attrD: Option[String])
case class B(attrC: List[C])
case class A(attrB: Option[B])
val objectA = Some(A(Some(B(List(C(Some("foo")), C(Some("bar")))))))
// returns Some(foo)
objectA flatMap (_.attrB) flatMap (_.attrC.headOption) flatMap (_.attrD)
val objectA2 = Some(A(Some(B(List()))))
// returns None
objectA2 flatMap (_.attrB) flatMap (_.attrC.headOption) flatMap (_.attrD)
你也可以使用 for comprehensions 而不是 flatMap
(因为理解被脱糖到 flatMap
/map
链),但在这种情况下它实际上可读性较差(相反的情况通常是正确的)因为在每一步你都必须引入一个绑定然后在下一步引用它:
for ( a <- objectA; b <- a.attrB; c <- b.attrC.headOption; d <- c.attrD ) yield d
如果您愿意使用 ScalaZ,另一种解决方案是使用 >>=
代替 flatMap
,这样可以缩短一些:
val objectA = Option(A(Some(B(List(C(Some("foo")), C(Some("bar")))))))
// returns Some(foo)
objectA >>= (_.attrB) >>= (_.attrC.headOption) >>= (_.attrD)
这与使用 flatMap
完全一样,只是更短。
我相信你想要这个,
val deepNestedVal = objectA.get.attrB.get.attrC.get.attrD.get
我认为更可取的方式是使用 for-comprehension,
val deepNestedVal = for {
val1 <- objectA
val2 <- val1.attrB
val3 <- val2.attrC
val4 <- val3.attrD
} yield val4
其他方法是使用 flatMap
,如@Régis Jean-Gilles
的回答所示
我有许多嵌套对象,全部包含在 Scala Option 类型中。在我的项目的其他地方,每次调用 .get
时,我都必须调用一个嵌入大约 5 层深的属性(其中一些是列表)。这样我最终得到如下所示的内容:
objectA.get.attrB.get.attrC.get(0).attrD.get
除了一系列 .get
调用(我不确定这是否是理想的)之外,我没有以这种方式实现很多错误处理,如果任何属性为空,那么整个事情就会失败分开。鉴于嵌套调用,如果我将其限制为如上所述的单行,我也只能在最后使用一次 .getOrElse
。
是否有任何在 Scala 中使用选项类型的建议方法?
在这种情况下,开箱即用的最易读的解决方案(即不编写辅助方法)可以说是链式调用 Option.flatMap
:
objectA flatMap (_.attrB) flatMap (_.attrC.headOption) flatMap (_.attrD)
通过使用 flatMap
,如果链中的任何选项是 None
,您最终会得到 None
(与 [=18 不同,没有例外) =] 在 None
).
举例:
case class C(attrD: Option[String])
case class B(attrC: List[C])
case class A(attrB: Option[B])
val objectA = Some(A(Some(B(List(C(Some("foo")), C(Some("bar")))))))
// returns Some(foo)
objectA flatMap (_.attrB) flatMap (_.attrC.headOption) flatMap (_.attrD)
val objectA2 = Some(A(Some(B(List()))))
// returns None
objectA2 flatMap (_.attrB) flatMap (_.attrC.headOption) flatMap (_.attrD)
你也可以使用 for comprehensions 而不是 flatMap
(因为理解被脱糖到 flatMap
/map
链),但在这种情况下它实际上可读性较差(相反的情况通常是正确的)因为在每一步你都必须引入一个绑定然后在下一步引用它:
for ( a <- objectA; b <- a.attrB; c <- b.attrC.headOption; d <- c.attrD ) yield d
如果您愿意使用 ScalaZ,另一种解决方案是使用 >>=
代替 flatMap
,这样可以缩短一些:
val objectA = Option(A(Some(B(List(C(Some("foo")), C(Some("bar")))))))
// returns Some(foo)
objectA >>= (_.attrB) >>= (_.attrC.headOption) >>= (_.attrD)
这与使用 flatMap
完全一样,只是更短。
我相信你想要这个,
val deepNestedVal = objectA.get.attrB.get.attrC.get.attrD.get
我认为更可取的方式是使用 for-comprehension,
val deepNestedVal = for {
val1 <- objectA
val2 <- val1.attrB
val3 <- val2.attrC
val4 <- val3.attrD
} yield val4
其他方法是使用 flatMap
,如@Régis Jean-Gilles