2013-02-27 16 views
7

Mam model, który ma kilka pól opcji, które zawierają inne pola opcji. Na przykład:Obiekt opcji Scala wewnątrz innego obiektu 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]) 

Odbieram te dane z zewnętrznych JSON i czasami te dane mogą zawierać wartości null na to, że przyczyną takiej konstrukcji modelu.

Pytanie brzmi: jaki jest najlepszy sposób na uzyskanie najgłębszego pola?

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

Powyższa metoda wygląda naprawdę brzydko i może powodować wyjątek, jeśli jeden z obiektów będzie Brak. Szukałem w bibliotece Scalaz, ale nie znalazłem lepszego sposobu na zrobienie tego.

Wszelkie pomysły? Z góry dzięki.

+2

Tylko uwaga ale woun flatMap” t działają tak, jak podano poniżej kilka razy. Powinien to być 'First.second.flatMap (_. Third.flatMap (_. NumberOfSmth)). Get' i nadal może rzutować i być może wyjątek – korefn

+0

Rzeczywiście, dziękuję. Dziękuję wszystkim za odpowiedzi, znalazłem to, czego szukałem. – psisoyev

Odpowiedz

14

Rozwiązaniem jest użycie Option.map i Option.flatMap:

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

lub równoważny (patrz aktualizacjęna końcu tej odpowiedzi):

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

ta zwraca Option[Int] (pod warunkiem że numberOfSmth zwraca Int). Jeśli którakolwiek z opcji w łańcuchu wywołań to None, wynikiem będzie None, w przeciwnym razie będzie to Some(count), gdzie count jest wartością zwróconą przez numberOfSmth.

Oczywiście może to być bardzo brzydkie. Z tego powodu scala obsługuje dla pojmowania jako cukru syntaktycznego.Powyższe może być zapisane jako:

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

Który jest prawdopodobnie ładniejsze (zwłaszcza jeśli nie są jeszcze przyzwyczajeni do map/flatMap wszędzie, a na pewno będzie miało miejsce po jakimś czasie, używając Scala) i generuje dokładne ten sam kod pod maską.

Więcej tle, można sprawdzić to inne pytanie: What is Scala's yield?

UPDATE: Dzięki Ben James za wskazanie, że flatMap jest łączne. Innymi słowy, x flatMap(y flatMap z))) jest taki sam jak x flatMap y flatMap z. Podczas gdy ta ostatnia zazwyczaj nie jest krótsza, ma tę zaletę, że unika zagnieżdżania się, co jest łatwiejsze do naśladowania.

Oto ilustracja w REPL (z 4 stylów są równoważne, przy czym dwa pierwsze używając flatMap gniazdowania, dwa pozostałe za pomocą płaskich łańcuchów 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

Nie musisz używać brzydkiego zagnieżdżania: z powodu asocjatywności 'flatMap',' flatMap (b flatMap c) 'jest równoważne' flatMap b flatMap c' –

+0

Dzięki, masz rację. Zazwyczaj je zagnieżdżam, ponieważ naśladuje rzeczywistą strukturę tego, co jest mapowane/flatMapped over (które ** jest ** zagnieżdżone, jak w tym przypadku). Ale prawdą jest, że jest bardziej czytelny jako płaski łańcuch "płaskiej mapy". –

+1

Należy zauważyć, że brak zagnieżdżania map płaskich ma tę wadę, że nie jest w stanie użyć parametru lambda w zagnieżdżonych wyrażeniach lambda, więc chociaż działa w tym przypadku, nie polecam go ogólnie. Jest również wolniejszy i tworzy więcej obiektów funkcyjnych. –

4

Można to zrobić przez połączeń łańcuchowym do flatMap:

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

Można również zrobić to z do-zrozumienia, ale jest to bardziej szczegółowe, jak to zmusza cię nazwać każdą wartość pośrednią:

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

nie ma potrzeby scalaz:

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

Alternatywnie ty c zastosowania objętego zagnieżdżone flatMaps

0

myślę, że jest to przesada dla swojego problemu, ale tylko jako ogólne odniesienie:

Ta zagnieżdżona problemem dostępu skierowana jest przez pojęcie zwane soczewek. Zapewniają ładny mechanizm dostępu do zagnieżdżonych typów danych za pomocą prostego składu. Jako wprowadzenie możesz sprawdzić np. this SO answer lub this tutorial. Pytanie, czy użycie soczewek w twoim przypadku ma sens, to czy musisz również wykonać wiele aktualizacji w zagnieżdżonej strukturze opcji (uwaga: aktualizacja nie w sensie zmiennym, ale zwracającym nową, zmodyfikowaną, ale niezmienną instancję). Bez soczewek prowadzi to do długiego zagnieżdżonego kodu klasy Case copy. Jeśli nie musisz wcale aktualizować, pozostanę przy om-nom-nom's suggestion.

+2

@Dvvoter: Czy mógłbyś wyjaśnić sprawę? Biorąc pod uwagę, że OP musi aktualizować tę zagnieżdżoną strukturę, myślę, że powinno mu się umożliwić skierowanie OP do koncepcji soczewek jako alternatywnego rozwiązania? – bluenote10

Powiązane problemy