2013-02-27 13 views
7

Ho un modello, che ha alcuni campi Opzione, che contengono altri campi Opzione. Per esempio:Oggetto Opzione Scala all'interno di un altro Oggetto opzione

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

sto ricevendo questi dati da esterni JSON di e, a volte questi dati possono contenere nulla di, che è stato il motivo di tale progettazione del modello.

Quindi la domanda è: qual è il modo migliore per ottenere un campo più profondo?

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

Il metodo sopra sembra davvero brutto e potrebbe causare un'eccezione se uno degli oggetti sarà Nessuno. Stavo osservando la libreria di Scalaz, ma non ho trovato un modo migliore per farlo.

Qualche idea? Grazie in anticipo.

+2

Solo una nota, ma il woun flatMap' t lavorare come indicato di seguito più volte. Dovrebbe essere 'First.second.flatMap (_. Third.flatMap (_. NumberOfSmth)). Get' ed è ancora possibile generare ed eccezione – korefn

+0

Infatti, grazie. Grazie a tutti per le vostre risposte, ho trovato quello che stavo cercando. – psisoyev

risposta

14

La soluzione è quella di utilizzare Option.map e Option.flatMap:

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

O l'equivalente (vedere la aggiornamento alla fine di questa risposta):

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

Ciò restituisce un Option[Int] (fornito che numberOfSmth restituisce un Int). Se una delle opzioni nella catena di chiamate è None, il risultato sarà None, altrimenti sarà Some(count) dove count è il valore restituito da numberOfSmth.

Ovviamente questo può diventare brutto molto velocemente. Per questo motivo scala supporta per le comprensibili come zucchero sintattico.Quanto sopra può essere riscritta come:

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

che è probabilmente più bello (soprattutto se non siete ancora abituati a vedere map/flatMap ovunque, come sarà certamente il caso dopo un po 'utilizzando Scala), e genera l'esatto stesso codice sotto il cofano.

per più di fondo, si può controllare questo altro problema: What is Scala's yield?

UPDATE: Grazie a Ben James per aver ricordato che flatMap è associativa. In altre parole, x flatMap(y flatMap z))) corrisponde a x flatMap y flatMap z. Mentre quest'ultimo di solito non è più breve, ha il vantaggio di evitare qualsiasi nidificazione, che è più facile da seguire.

Ecco alcune illustrazione nel REPL (i 4 stili sono equivalenti, con i primi due con flatMap nidificazione, le altre due con catene piane di 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

Non è necessario utilizzare il brutto nidificazione: a causa dell'associatività di 'flatMap',' una flatMap (b flatMap c) 'è equivalente a' a flatMap b flatMap c' –

+0

Grazie, hai ragione. Di solito li nidisco perché questo imita la struttura effettiva di ciò che è mappped/flatMapped over (che ** è ** annidato, come in questo caso). Ma è vero che è più leggibile come una catena piatta di 'flatMap'. –

+1

Si noti che non annidare flatMaps ha lo svantaggio di non essere in grado di utilizzare un parametro lambda nelle espressioni lambda annidate, quindi, anche se funziona in questo caso, non lo consiglierei generico. È anche più lento e crea più oggetti funzione. –

4

questo può essere fatto da chiamate concatenamento di flatMap:

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

Si può anche fare questo con la comprensione per-, ma è più prolisso, perché costringe a nominare ogni valore intermedio:

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

non v'è alcuna necessità di scalaz:

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

in alternativa si c Un uso annidato flatMaps

0

credo che sia un peso inutile per il vostro problema, ma solo come riferimento generale:

Questo problema di accesso nidificato viene affrontato da un concetto chiamato lenti. Forniscono un buon meccanismo per accedere a tipi di dati annidati per composizione semplice. Come introduzione si potrebbe voler verificare l'istanza this SO answer o this tutorial. La questione se abbia senso utilizzare gli obiettivi nel tuo caso è se devi anche eseguire molti aggiornamenti nella struttura delle opzioni annidata (nota: aggiornamento non in senso mutevole, ma restituendo una nuova istanza modificata ma immutabile). Senza lenti questo porta al codice di classe copy della classe caso nidificato. Se non è necessario aggiornare affatto, mi attenersi a om-nom-nom's suggestion.

+2

@Downvoter: ti dispiacerebbe spiegare il downvote? Dato che l'OP deve aggiornare molto questa struttura annidata, penso che dovrebbe essere permesso di indirizzare l'OP al concetto di Lenti come soluzione alternativa? – bluenote10

Problemi correlati