Scala Объект Option внутри другого объекта Option

У меня есть модель, которая имеет несколько полей Option, которые содержат другие поля Option. Например:

case class First(second: Option[Second], name: Option[String])
case class Second(third: Option[Third], title: Option[String])
case class Third(numberOfSmth: Option[Int])

Я получаю эти данные из внешних JSON, и иногда эти данные могут содержать нулевые значения, что было причиной такого дизайна модели.

Итак, вопрос в том, как лучше всего получить самое глубокое поле?

First.get.second.get.third.get.numberOfSmth.get

Вышеуказанный метод выглядит действительно некрасиво и может вызвать исключение, если один из объектов будет None. Я искал в Scalaz lib, но не нашел лучшего способа сделать это.

Есть идеи? Заранее спасибо.

4 ответа

Решение

Решение заключается в использовании Option.map а также Option.flatMap:

First.flatMap(_.second.flatMap(_.third.map(_.numberOfSmth)))

Или эквивалент (см. Обновление в конце этого ответа):

First flatMap(_.second) flatMap(_.third) map(_.numberOfSmth)

Это возвращает Option[Int] (при условии, что numberOfSmth возвращает Int). Если какой-либо из параметров в цепочке вызовов Noneрезультат будет Noneиначе будет Some(count) где count это значение, возвращаемое numberOfSmth,

Конечно, это может быть очень быстро. По этой причине scala поддерживает понимание как синтаксический сахар. Выше можно переписать как:

for { 
  first <- First
  second <- first .second
  third <- second.third
} third.numberOfSmth

Что, возможно, лучше (особенно если вы еще не привыкли видеть map/flatMap везде, что, безусловно, будет иметь место через некоторое время с использованием Scala), и генерирует точно такой же код под капотом.

Для получения дополнительной информации, вы можете проверить этот другой вопрос: Какова доходность Scala?

ОБНОВЛЕНИЕ: Спасибо Бену Джеймсу за то, что он указал, что flatMap ассоциативен. Другими словами x flatMap(y flatMap z))) такой же как x flatMap y flatMap z, Хотя последний, как правило, не короче, он имеет преимущество в том, что избегает вложений, за которыми легче следить.

Вот некоторая иллюстрация в REPL (4 стиля эквивалентны, первые два используют вложение flatMap, остальные два - плоские цепочки flatMap):

scala> val l = Some(1,Some(2,Some(3,"aze")))
l: Some[(Int, Some[(Int, Some[(Int, String)])])] = Some((1,Some((2,Some((3,aze))))))
scala> l.flatMap(_._2.flatMap(_._2.map(_._2)))
res22: Option[String] = Some(aze)
scala> l flatMap(_._2 flatMap(_._2 map(_._2)))
res23: Option[String] = Some(aze)
scala> l flatMap(_._2) flatMap(_._2) map(_._2)
res24: Option[String] = Some(aze)
scala> l.flatMap(_._2).flatMap(_._2).map(_._2)
res25: Option[String] = Some(aze)

Нет необходимости в скалязе:

for { 
  first  <- yourFirst
  second <- f.second
  third  <- second.third
  number <- third.numberOfSmth
} yield number

В качестве альтернативы вы можете использовать вложенные flatMaps

Это может быть сделано путем объединения вызовов flatMap:

def getN(first: Option[First]): Option[Int] =
  first flatMap (_.second) flatMap (_.third) flatMap (_.numberOfSmth)

Вы также можете сделать это для простоты понимания, но оно более многословно, так как заставляет вас называть каждое промежуточное значение:

def getN(first: Option[First]): Option[Int] =
  for {
    f <- first
    s <- f.second
    t <- s.third
    n <- t.numberOfSmth
  } yield n

Я думаю, что это излишне для вашей проблемы, но просто для справки:

Эта проблема с вложенным доступом решается с помощью концепции, называемой линзами. Они предоставляют удобный механизм доступа к вложенным типам данных с помощью простой композиции. В качестве введения вы, возможно, захотите проверить, например, этот SO-ответ или это руководство. Вопрос о том, имеет ли смысл использовать линзы в вашем случае, заключается в том, нужно ли вам также выполнять много обновлений в структуре вложенных опций (примечание: обновление не в изменяемом смысле, а возвращает новый измененный, но неизменный экземпляр). Без линз это приводит к длительному вложенному классу copy код. Если вам вообще не нужно обновляться, я бы придерживался предложения om-nom-nom.

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