Рекомендации для вложенных полей опций 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

Другие вопросы по тегам