2013-02-27 2 views
7

У меня есть модель, которая имеет несколько полей Option, которые содержат другие поля Option. Например:Объект Scala 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, но не нашел лучшего способа сделать это.

Любые идеи? Спасибо заранее.

+2

Просто к сведению, но в Woun flatMap» t, как указано ниже несколько раз. Это должно быть 'First.second.flatMap (_. Third.flatMap (_. NumberOfSmth)). Get' и все еще может выбрасывать и исключать – korefn

+0

Действительно, спасибо. Спасибо всем за ваши ответы, я нашел то, что искал. – psisoyev

ответ

14

Решение заключается в использовании 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) и генерирует точный тот же код под капотом.

Для более фона, вы можете проверить это другой вопрос: What is Scala's yield?

UPDATE: Благодаря Бен Джеймс за указание на то, что 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) 
+3

Вам не нужно использовать уродливое вложение: из-за ассоциативности 'flatMap', flatMap (b flatMap c)' равнозначно 'flatMap b flatMap c' –

+0

Спасибо, вы очень правы. Я обычно их гнездю, потому что это имитирует фактическую структуру того, что отображается/flatMapped (которое ** является ** вложенным, как в этом случае). Но это правда, что это более читаемо, как плоская цепь «flatMap». –

+1

Обратите внимание, что не вложенность flatMaps имеет тот недостаток, что не может использовать параметр лямбда во вложенных лямбда-выражениях, поэтому, хотя он работает в этом случае, я бы не рекомендовал его вообще. Он также медленнее и создает больше объектов функций. –

4

Это может быть сделано путем построения цепочки вызовов 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 
10

Там нет необходимости для scalaz:

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

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

0

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

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

+2

@ Downvoter: Не могли бы вы объяснить нижний предел? Учитывая, что OP должен обновить эту вложенную структуру, я думаю, должно быть разрешено указывать OP на концепцию Lenses как альтернативное решение? – bluenote10

Смежные вопросы