Рекомендации для вложенных полей опций Scala?
У меня есть несколько вложенных объектов, все они обернуты в тип опции Scala. В другом месте моего проекта мне приходится вызывать атрибут, который имеет глубину около 5 уровней (некоторые из которых являются списками), каждый раз делая вызов .get
, Таким образом, я получаю что-то похожее на следующее:
objectA.get.attrB.get.attrC.get(0).attrD.get
Кроме серии .get
вызовов (что я не уверен, что это идеально), я не реализую много ошибок обработки таким образом, и если какой-либо из атрибутов будет пустым, то все это рушится. Учитывая вложенные вызовы, если бы я ограничил это однострочником, как указано выше, я также мог бы использовать только .getOrElse
один раз в конце.
Есть ли предлагаемые способы работы с типами Option в Scala?
2 ответа
В этом случае наиболее читаемым решением "из коробки" (то есть без написания вспомогательных методов), вероятно, будет цепочка обращений к Option.flatMap
:
objectA flatMap (_.attrB) flatMap (_.attrC.headOption) flatMap (_.attrD)
Используя flatMap
, если какой-либо из вариантов в цепочке None
, вы в конечном итоге None
в конце (не исключение в отличие от get
который будет дуть при вызове 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)
Вы также можете использовать для понимания вместо 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
Я думаю, что более предпочтительным способом было бы использовать для понимания,
val deepNestedVal = for {
val1 <- objectA
val2 <- val1.attrB
val3 <- val2.attrC
val4 <- val3.attrD
} yield val4
Другой способ заключается в использовании flatMap
как показано в ответе @Régis Jean-Gilles