Domanda

Io non sono in grado di capire il punto di classe Option[T] a Scala. Voglio dire, io non sono in grado di vedere qualsiasi advanages di None sopra null.

Per esempio, si consideri il codice:

object Main{
  class Person(name: String, var age: int){
    def display = println(name+" "+age)
  }

  def getPerson1: Person = {
    // returns a Person instance or null
  }

  def getPerson2: Option[Person] = {
    // returns either Some[Person] or None
  }

  def main(argv: Array[String]): Unit = {
    val p = getPerson1
    if (p!=null) p.display

    getPerson2 match{
      case Some(person) => person.display
      case None => /* Do nothing */
    }
  }
}

Ora supponiamo, il metodo ritorna getPerson1 null, la chiamata fatta al display sulla prima linea della main è destinato a fallire con NPE. Allo stesso modo se getPerson2 ritorna None, la chiamata display sarà di nuovo esito negativo con un errore simile.

Se è così, allora perché Scala complicare le cose con l'introduzione di un involucro nuovo valore (Option[T]) invece di seguire un approccio semplice utilizzato in Java?

UPDATE:

Ho modificato il mio codice come da @Mitch s ' suggerimento. Io non sono ancora in grado di vedere alcun particolare vantaggio di Option[T]. Devo verificare l'null eccezionali o None in entrambi i casi. : (

Se ho capito bene da @ di Michael risposta , è l'unico vantaggio di Option[T] è che racconta in modo esplicito il programmatore che questo metodo potrebbe restituire None ? E 'questo l'unico motivo alla base di questa scelta progettuale?

È stato utile?

Soluzione

Si otterrà il punto di Option meglio se si forza a voi stessi di mai, mai, uso get. Questo perché get è l'equivalente di "ok, mandami torna a null-terra".

Quindi, prendere quell'esempio del tuo. Come chiamereste display senza usare get? Qui ci sono alcune alternative:

getPerson2 foreach (_.display)
for (person <- getPerson2) person.display
getPerson2 match {
  case Some(person) => person.display
  case _ =>
}
getPerson2.getOrElse(Person("Unknown", 0)).display

Niente di tutto questo le alternative vi permetterà di chiamare display su qualcosa che non esiste.

Per quanto riguarda il motivo per cui esiste get, Scala non spiegano come il codice dovrebbe essere scritto. Potrebbe gentilmente spingere voi, ma se si vuole ricadere alcuna rete di sicurezza, è la vostra scelta.


È inchiodato qui:

  

è l'unico vantaggio di Opzione [T] è   che racconta in modo esplicito il   programmatore che questo metodo potrebbe   Nessuno tornare?

A parte il "solo". Ma permettetemi di ribadire che in un altro modo: principale vantaggio di Option[T] sopra T è digitare la sicurezza. Assicura che non sarà l'invio di un metodo T ad un oggetto che non può esistere, come il compilatore non ti consente di.

Hai detto che hai per verificare nullability in entrambi i casi, ma se si dimentica - o non si sa - è necessario verificare la presenza di null, sarà il compilatore dire? O sarà vostri utenti?

Naturalmente, a causa della sua interoperabilità con Java, Scala permette null proprio come fa Java. Quindi, se si utilizzano le librerie Java, se si utilizzano le librerie Scala scritti male, o se si utilizza scritto male biblioteche personali Scala, avrai ancora a che fare con i puntatori nulli.

Altri due vantaggi importanti della Option mi vengono in mente sono:

  • Documentazione: una firma tipo di metodo vi dirà se un oggetto è sempre restituito o meno

  • .
  • componibilità Monadica.

Quest'ultimo richiede molto più tempo per apprezzare appieno, e non è adatto a semplici esempi, che mostra solo la sua forza sul codice complesso. Quindi, io darò un esempio qui sotto, ma sono ben consapevole che difficilmente significa nulla, tranne per le persone che ricevono già.

for {
  person <- getUsers
  email <- person.getEmail // Assuming getEmail returns Option[String]
} yield (person, email)

Altri suggerimenti

Confronto:

val p = getPerson1 // a potentially null Person
val favouriteColour = if (p == null) p.favouriteColour else null

con:

val p = getPerson2 // an Option[Person]
val favouriteColour = p.map(_.favouriteColour)

La proprietà monadica bind , che appare nella Scala della mappa funzione , ci permette di catena di operazioni su oggetti senza preoccuparsi se essi sono 'null' o meno.

Prendete questo semplice esempio un po 'più avanti. Dire abbiamo voluto trovare tutti i colori preferiti di una lista di persone.

// list of (potentially null) Persons
for (person <- listOfPeople) yield if (person == null) null else person.favouriteColour

// list of Options[Person]
listOfPeople.map(_.map(_.favouriteColour))
listOfPeople.flatMap(_.map(_.favouriteColour)) // discards all None's

O forse vorremmo trovare il nome della sorella della madre del padre di una persona:

// with potential nulls
val father = if (person == null) null else person.father
val mother = if (father == null) null else father.mother
val sister = if (mother == null) null else mother.sister

// with options
val fathersMothersSister = getPerson2.flatMap(_.father).flatMap(_.mother).flatMap(_.sister)

Spero che questo getta una luce su come le opzioni possono rendere la vita un po 'più facile.

La differenza è sottile. Tenete a mente per essere veramente una funzione che deve restituire un valore - null non è realmente considerato un "valore di ritorno normale" in questo senso, più un tipo di fondale / nulla.

Ma, in un certo senso pratico, quando si chiama una funzione che restituisce opzionalmente qualcosa, si dovrebbe fare:

getPerson2 match {
   case Some(person) => //handle a person
   case None => //handle nothing 
}

Certo, si può fare qualcosa di simile con nulla - ma questo rende la semantica di chiamare getPerson2 evidente in virtù del fatto restituisce Option[Person] (una bella cosa pratica, altro che fare affidamento su qualcuno che legge il documento e ottenere un NPE perché non leggere il doc).

cercherò di scavare un programmatore funzionale che può dare una risposta più severo di me.

Per quanto mi riguarda le opzioni sono davvero interessanti quando maneggiato con la sintassi comprensione. Prendendo synesso dell'esempio precedente:

// with potential nulls
val father = if (person == null) null else person.father
val mother = if (father == null) null else father.mother
val sister = if (mother == null) null else mother.sister

// with options
val fathersMothersSister = for {
                                  father <- person.father
                                  mother <- father.mother
                                  sister <- mother.sister
                               } yield sister

Se uno qualsiasi di assegnazione sono None, il fathersMothersSister sarà None ma nessun NullPointerException verrà sollevata. Si può quindi tranquillamente passare fathersMothersSisterto una funzione di prendere parametri opzionali, senza preoccuparsi. in modo da non controllare per nulla e non si cura delle eccezioni. Confronta questo per la versione Java presentato in synesso esempio.

Hai abbastanza potenti funzionalità di composizione con Opzione:

def getURL : Option[URL]
def getDefaultURL : Option[URL]


val (host,port) = (getURL orElse getDefaultURL).map( url => (url.getHost,url.getPort) ).getOrElse( throw new IllegalStateException("No URL defined") )

Forse qualcun altro ha fatto presente, ma non l'ho visto:

Un vantaggio di pattern-matching con l'opzione [T] vs. controllo nulla è che l'opzione è una classe chiusa, in modo che il compilatore Scala emetterà un avviso se si trascura di codificare sia la parte o l'Nessuno caso. C'è una bandiera compilatore per il compilatore che trasformerà gli avvisi in errori. Quindi è possibile prevenire il fallimento per gestire il caso "non esiste" in fase di compilazione, piuttosto che in fase di esecuzione. Questo è un enorme vantaggio rispetto all'utilizzo del valore nullo.

Non è lì per aiutare ad evitare un controllo nullo, è lì per forzare un controllo nullo. Il punto diventa chiaro quando la classe dispone di 10 campi, due dei quali potrebbero essere nulli. E il sistema dispone di 50 altre classi simili. Nel mondo Java, si cerca di evitare che NPE su quei campi che utilizzano una combinazione di horesepower mentale, convenzione di denominazione, o forse anche le annotazioni. E ogni dev Java non riesce a questo in misura significativa. La classe di opzione rende non solo i valori "Null" visivamente chiaro a tutti gli sviluppatori che cercano di capire il codice, ma permette al compilatore di far rispettare questo contratto precedentemente non detto.

[copiato da questo commento Daniel Spiewak ]

  

Se l'unico modo per utilizzare Option erano   al pattern match al fine di ottenere   valori fuori, allora sì, sono d'accordo che   non migliora in tutto nullo.   Tuttavia, vi state perdendo una grande classe * *   della sua funzionalità. Il solo   motivo valido per usare Option è   se si sta utilizzando il suo ordine superiore   funzioni di utilità. In effetti, si   necessario utilizzare la sua natura monadica.   Per esempio (supponendo una certa quantità   API trimming):

val row: Option[Row] = database fetchRowById 42
val key: Option[String] = row flatMap { _ get “port_key” }
val value: Option[MyType] = key flatMap (myMap get)
val result: MyType = value getOrElse defaultValue
     

Non, non era così abile? Noi possiamo   in realtà fare molto meglio se si usa   for-comprensioni:

val value = for {
row <- database fetchRowById 42
key <- row get "port_key"
value <- myMap get key
} yield value
val result = value getOrElse defaultValue
     

Si noterà che siamo * mai *   controllando in modo esplicito per nulla, Nessuno o   nessuno dei suoi simili. Il punto di   Opzione è quella di evitare qualsiasi di quel   controllo. Voi soli calcoli di stringa   lungo e spostare verso il basso la linea finché non si   * Veramente * bisogno di ottenere un valore fuori. A   quel punto, si può decidere se o   Non si vuole fare il controllo esplicito   (Che si dovrebbe non hanno a che fare),   fornire un valore predefinito, genera un   eccezioni, ecc.

     

Non ho mai, mai fare qualsiasi corrispondenza esplicita   contro Option, e so che un sacco di   altri sviluppatori Scala che sono nel   stessa barca. David Pollak menzionato al   me solo l'altro giorno che egli utilizza   tale corrispondenza esplicito su Option (o   Box, nel caso di sollevamento), come segno   che lo sviluppatore che ha scritto il codice   non comprendere appieno la lingua   e la sua libreria standard.

     

Non voglio dire di essere un martello troll, ma   si ha realmente bisogno di guardare a come   caratteristiche del linguaggio sono * davvero * usati   in pratica prima di bash come   inutili. Sono assolutamente d'accordo che   Opzione è abbastanza uncompelling come * voi *   usato, ma non lo si utilizza la   modo in cui è stato progettato.

Un punto che nessun altro qui sembra aver sollevato è che, mentre si può avere un riferimento null, non v'è una distinzione introdotta da Option.

Questo è si può avere Option[Option[A]], che sarebbe abitato da None, Some(None) e Some(Some(a)) dove a è uno dei soliti abitanti di A. Questo significa che se si dispone di un qualche tipo di contenitore, e vuole essere in grado di memorizzare puntatori nulli in essa, e farli uscire, è necessario passare indietro qualche valore booleano in più per sapere se effettivamente ottenuto un valore fuori. Verruche come questo abbondano nei contenitori Java API e alcune varianti senza blocchi non si può nemmeno fornire loro.

null è una costruzione una tantum, non comporre con se stesso, è disponibile solo per i tipi di riferimento, e ti costringe a ragionare in modo non totale.

Per esempio, quando si controlla

if (x == null) ...
else x.foo()

si deve portare in giro nella tua testa tutto il ramo else che x != null e che questo è già stato controllato. Tuttavia, quando si utilizza qualcosa come opzione

x match {
case None => ...
case Some(y) => y.foo
}

so Y non è Noneby costruzione - e sapreste che non era null neanche, se non fosse per Hoare di billion dollaro errore .

L'opzione [T] è una monade, che è davvero utile quando si utilizzando le funzioni di ordine superiore per manipolare i valori.

Ti consiglio di leggere gli articoli elencati di seguito, sono davvero buoni articoli che mostrano il motivo per cui l'opzione [T] è utile e come può essere utilizzato in modo funzionale.

L'aggiunta a rompicapo di Randall di una risposta , capire perché il potenziale assenza di un valore è rappresentato da Option richiede comprensione di ciò che Option condivide con molti altri tipi di Scala-specificamente, i tipi di modellazione monadi. Se uno rappresenta l'assenza di un valore con null, tale distinzione assenza-presenza non può partecipare contratti condivise da altri tipi monadici.

Se non sai cosa sei monadi, o se non si nota come sono rappresentati nella biblioteca di Scala, non si vedrà cosa Option gioca insieme, e non si può vedere ciò che sei perdersi. Ci sono molti vantaggi di utilizzare Option invece di null che sarebbe degno di nota, anche in assenza di qualsiasi concetto monade (discuto alcuni di loro nel "Costo di opzione / Alcuni vs null" scala-user mailing elenco delle discussioni qui ), ma parlando di che l'isolamento è un po 'come parlare di tipo iteratore un particolare di implementazione lista collegata, chiedendosi perché è necessario, per tutto il tempo perdendo il / iteratore interfaccia più generale contenitore / algoritmo. C'è un'interfaccia più ampio al lavoro anche qui, e Option fornisce un modello di presenza-e-assenza di tale interfaccia.

Credo che la chiave si trova nella risposta di Synesso: L'opzione è non in primo luogo utile come alias ingombrante per nulla, ma come un oggetto a tutti gli effetti che possono poi dare una mano con la vostra logica <. / p>

Il problema con null è che è il mancanza di un oggetto. Non ha metodi che potrebbero aiutare a trattare con esso (anche se, come un progettista di linguaggi è possibile aggiungere sempre più lunghi elenchi di funzionalità per la lingua che emulano un oggetto se davvero voglia).

Una cosa opzione può fare, come hai dimostrato, è quello di emulare null; è quindi necessario per verificare lo straordinario valore "None" invece del valore straordinario "null". Se si dimentica, in entrambi i casi, accadranno brutte cose. Opzione non rendono meno probabile che accada per caso, dal momento che si deve digitare "get" (che dovrebbe ricordare che potrebbe essere null, ehm, voglio dire nessuno), ma questo è un piccolo beneficio in cambio di un oggetto wrapper supplementare.

dove opzione davvero comincia a mostrare la sua forza sta aiutando avete a che fare con il concetto di I-voluto-qualcosa-ma-io-non-realtà-have-one.

Consideriamo alcune cose che si potrebbe desiderare di fare con le cose che potrebbero essere nullo.

Forse si vuole impostare un valore predefinito se si dispone di un nulla. Mettiamo a confronto Java e Scala:

String s = (input==null) ? "(undefined)" : input;
val s = input getOrElse "(undefined)"

Al posto di un po 'ingombrante:? Costruire abbiamo un metodo che si occupa con l'idea di "utilizzare un valore predefinito se sono nulla". Questo pulisce il codice un po '.

Forse si vuole creare un nuovo oggetto solo se si dispone di un valore reale. Confronto:

File f = (filename==null) ? null : new File(filename);
val f = filename map (new File(_))

Scala è leggermente più corta ed evita fonti di errore di nuovo. Quindi prendere in considerazione il beneficio complessivo quando hai bisogno di cose a catena insieme come mostrato negli esempi di Synesso, Daniel, e paradigmatico.

Non è un stragrande di miglioramento, ma se si aggiunge tutto, vale la pena ovunque salvare codice molto ad alte prestazioni (in cui si desidera evitare anche il piccolo sovraccarico di creare il Alcuni (x) oggetto wrapper).

L'utilizzo partita non è davvero così utile da solo se non come un dispositivo per avvisare l'utente in merito al caso nulla / Nessuno. Quando si è veramente utile è quando si avvia il concatenamento che, per esempio, se si dispone di un elenco di opzioni:

val a = List(Some("Hi"),None,Some("Bye"));
a match {
  case List(Some(x),_*) => println("We started with " + x)
  case _ => println("Nothing to start with.")
}

Ora si arriva a piegare le Nessuno dei casi e la Lista-is-empty casi tutti insieme in un unico prospetto a portata di mano che tira fuori esattamente il valore che si desidera.

valori di ritorno Null sono presenti solo per la compatibilità con Java. Non li dovrebbe usare in altro modo.

E 'davvero una questione di stile di programmazione. Utilizzo di Java funzionale, o scrivendo i propri metodi di supporto, si potrebbe avere la funzionalità opzione, ma non abbandonare il linguaggio Java:

http://functionaljava.org/examples/#Option.bind

Solo perché Scala include di default non lo rende speciale. La maggior parte degli aspetti di linguaggi funzionali sono disponibili in quella biblioteca e possono coesistere bene con altro codice Java. Così come è possibile scegliere di programmare Scala con i null è possibile scegliere di programmare Java senza di loro.

Ammettendo in anticipo che si tratta di una risposta glib, l'opzione è una monade.

In realtà io condivido il dubbio con voi. A proposito di Opzione mi dà fastidio veramente che 1) non v'è un sovraccarico di prestazioni, in quanto v'è un lor di "alcuni" involucri creati everywehre. 2) Devo usare un sacco di alcuni e l'opzione nel mio codice.

Quindi, per vedere i vantaggi e gli svantaggi di questa decisione di progettazione lingua dovremmo prendere in considerazione delle alternative. Come Java semplicemente ignora il problema di supporto di valori Null, non è un'alternativa. L'attuale alternativa fornisce linguaggio di programmazione Fantom. Ci sono tipi nullable e non-nullable lì e?. ?: Operatori, invece di mappa di Scala / flatMap / getOrElse. Vedo i seguenti proiettili nel confronto:

vantaggio di Opzione:

  1. linguaggio più semplice - non costrutti del linguaggio necessario ulteriore
  2. uniforme con altri tipi monadici

vantaggio di Nullable:

  1. sintassi più breve in casi tipici
  2. una migliore performance (come non è necessario per creare nuovi oggetti di opzione e lambda per la mappa, flatMap)

Quindi, non v'è alcun evidente vincitore qui. E ancora una nota. Non v'è alcun vantaggio sintattico principale per l'utilizzo di opzione. È possibile definire qualcosa come:

def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value)

In alternativa, utilizzare alcune conversioni implicite per ottenere la sintassi pritty con i puntini.

Il vero vantaggio di avere tipi di opzioni esplicite è che siete in grado di non usarli nel 98% di tutti i luoghi, e quindi precludere staticamente eccezioni nulli. (E negli altri 2% il sistema di tipo ricorda di controllare correttamente quando effettivamente accedervi.)

Un'altra situazione in cui funziona l'opzione, è in situazioni in cui i tipi non sono in grado di avere un valore nullo. Non è possibile memorizzare nulla in un int, float, double, ecc valore, ma con un opzione è possibile utilizzare l'Nessuno.

In Java, si avrebbe bisogno di utilizzare le versioni in scatola (Integer, ...) di quei tipi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top