2012-01-20 10 views
7

Sono molto nuovo a Scala e sto ancora cercando di abituarmi alla sintassi e allo stile, quindi questa è probabilmente una domanda molto semplice.Come faccio a separare Classi di casi piene di Opzioni in Scala

sto lavorando con una base di codice dove ci sono un sacco di classi case popolate con opzioni in questo modo:

case class Person(
    pants: Option[Pants] 
) 
case class Pants(
    pocket: Option[Pocket] 
) 
case class Pocket(
    cash: Option[Cash] 
) 
case class Cash(
    value: String = "zilch" 
) 

Nell'esempio precedente, come è possibile fare per restituire quanti soldi è in una Person ' s PantsPocket, se stanno davvero indossando pantaloni ... con le tasche e se hanno soldi?

risposta

8

un grande momento per for-comprehensions:

val someCash: Option[Cash] = 
    for(pants <- somePerson.pants; 
     pocket <- pants.pocket; 
     cash <- pocket.cash) yield cash 

modo equivalente si può scrivere la seguente, per il quale il primo codice è zucchero sintattico (ignorando alcune sottigliezze):

val someCash: Option[Cash] = 
    somePerson.pants.flatMap(_.pocket.flatMap(_.cash)) 

(io non sono totalmente sicuro di poter scrivere l'ultima espressione usando i caratteri jolly _, come ho fatto io). risposta

+0

Impressionante, grazie! L'approccio per la comprensione è in realtà esattamente quello che stavo cercando di fare, ma la struttura con cui sto lavorando non è pulita come nell'esempio che ho fornito sopra. Almeno questo conferma che sono sulla strada giusta. –

2

di ziggystar è quello che vorrei utilizzare, ma per completezza, pattern matching può anche essere utilizzato, ad esempio,

val someCash: Option[Cash] = person match { 
    case Person(Some(Pants(Some(Pocket(Some(cash)))))) => Some(cash) 
    case _ => None 
} 
6

La domanda non ha menzionato modifica i dati, ma quando è necessario fare questo si scopre rapidamente che la libreria Scala non ha gli strumenti per semplificare (quando i dati sono immutabili). Se non l'hai ancora provato, prova a scrivere una funzione che sostituirà o modificherà lo value dello Cash tenuto da un Person, utilizzando i tipi definiti nella domanda.

Come descritto in Asymmetric Lenses in Scala di Tony Morris, gli obiettivi sono una soluzione appropriata a questo problema.

Ecco un esempio di come si può accedere e aggiornare la value di Cash utilizzando le lenti (parziale) implementazioni di una persona Lens e PLens dal ramo scalaz-seven di Scalaz.

In primo luogo, un numero di piastre: definire l'istanza di Lente per ciascun campo delle classi di casi. A @[email protected] B significa lo stesso di Lens[A, B].

val pants: Person @[email protected] Option[Pants] = 
    lensG(_.pants, p => ps => p.copy(pants = ps)) 

val pocket: Pants @[email protected] Option[Pocket] = 
    lensG(_.pocket, ps => p => ps.copy(pocket = p)) 

val cash: Pocket @[email protected] Option[Cash] = 
    lensG(_.cash, p => c => p.copy(cash = c)) 

val value: Cash @[email protected] String = 
    lensG(_.value, c => v => c.copy(value = v)) 

Non possiamo comporre tutti questi obiettivi, però, perché la maggior parte dei campi sono avvolti in Option tipi.

Lenti parziali salvato: questi consentono di accedere e aggiornare le distinte di una struttura che non può esistere, come il valore di un SomeOption, o head di un List.

Possiamo usare la funzione somePLens da Scalaz 7 per creare un obiettivo parziale che visualizza ciascun campo facoltativo.Per comporre un obiettivo parziale con uno dei nostri obiettivi regolari, tuttavia, è necessario accedere all'istanza dell'obiettivo parziale equivalente per l'obiettivo normale, utilizzando il metodo partial esistente su ogni Lens.

// @-? is an infix type alias for PLens 
val someCash: Pocket @-? Cash = cash.partial andThen somePLens 

scala> someCash.get(Pocket(Some(Cash("zilch")))) 
res1: Option[Cash] = Some(Cash(zilch)) 

Allo stesso modo, possiamo creare il nostro obiettivo parziale la visualizzazione della liquidità detenuta da un Person componendo tutte le istanze partial nostre lenti, e sandwich istanze di somePLens. Qui, ho utilizzato l'operatore <=<, un alias per andThen (che equivale a compose con gli operandi attivati).

val someCashValue: Person @-? String = 
    pants.partial <=< somePLens <=< 
    pocket.partial <=< somePLens <=< 
    cash.partial <=< somePLens <=< 
    value.partial 

Creazione di un'istanza Person con cui giocare:

val ben = Person(Some(Pants(Some(Pocket(Some(Cash("zilch"))))))) 

Utilizzando la lente parziale accedere al valore del denaro che ho:

scala> someCashValue.get(ben) 
res2: Option[String] = Some(zilch) 

Utilizzando la lente parziale modificare il valore :

scala> someCashValue.mod(_ + ", zero, nada", ben) 
res3: Person = Person(Some(Pants(Some(Pocket(Some(Cash(zilch, zero, nada))))))) 
(!) 0

Ora, se io sono non indossare i pantaloni, possiamo vedere come un tentativo di modificare il valore dei miei soldi non avrà alcun effetto:

scala> val ben = Person(None) 
ben: Person = Person(None) 

scala> someCashValue.mod(_ + ", zero, nada", ben) 
res4: Person = Person(None) 
12

Scalaz 7 è cambiato un po 'così ecco un altro esempio:

object PartialLensExample extends App { 

    import scalaz._ 
    import Lens._ 
    import PLens._ 


    case class Bar(blub: Option[String]) 
    case class Foo(bar: Option[Bar]) 

    // normal lenses for getting and setting values 
    val fooBarL: Foo @> Option[Bar] = lensg(foo ⇒ bar ⇒ foo.copy(bar = bar), _.bar) 
    val barBlubL: Bar @> Option[String] = lensg(bar ⇒ blub ⇒ bar.copy(blub = blub), _.blub) 

    // compose the above as 'Partial Lenses', >=> is just an alias for 'andThen' 
    val fooBarBlubL: Foo @?> String = fooBarL.partial >=> somePLens >=> barBlubL.partial >=> somePLens 

    // try it 
    val foo = Foo(Some(Bar(Some("Hi")))) 

    println(fooBarBlubL.get(foo)) // Some(Hi) 

    println(fooBarBlubL.set(foo, "Bye")) //Foo(Some(Bar(Some(Bye)))) 

    // setting values 
    val foo2 = Foo(None) 
    println(fooBarL.set(foo2, Some(Bar(None)))) // Foo(Some(Bar(None))) 

} 
+1

Ottima risposta, ma un grosso ostacolo qui quando si impostano questi valori annidati è: cosa succede se alcuni valori sono 'Nessuno'? Il 'PLens' nel tuo esempio permetterà solo 'blub: Option [String]' da assegnare se 'blub' _already ha' Some' value_. Ho trovato transizioni dello stato monadico in grado di inizializzare i membri di livello superiore, ma non sono chiaro come sarebbe stato fatto per quelli più in basso. –

+1

hmm, sì. Ho aggiornato la risposta per l'impostazione di una barra su un Foo, ma vedo il problema di impostare il valore del blub in Bar: dovrò riflettere su questo. –

+0

[Una bogosità delle transizioni di stato che utilizza istanze di 'PLens'] (https://gist.github.com/michaelahlers/20ec194410f89422847fdd3a71777c69) è il meglio che potrei fare, inizializzando le proprietà in base alle necessità (il che ha senso, specialmente alla luce di [ risposta che coinvolge i pantaloni] (http://stackoverflow.com/a/9978488/700420) @ ben-james ha dato). Questo mi sembra troppo laborioso e non scala, quindi immagino che ci sia un approccio migliore. –

Problemi correlati