Usare Option ovunque sembra un po' imbarazzante.Sto facendo qualcosa di sbagliato?

StackOverflow https://stackoverflow.com/questions/2476835

  •  21-09-2019
  •  | 
  •  

Domanda

Come risultato di articoli che ho letto sul Option class che ti aiuta a evitare NullPointerException, ho iniziato a usarlo ovunque.Immagina qualcosa del genere:

var file:Option[File] = None

e più tardi quando lo uso:

val actualFile = file.getOrElse(new File("nonexisting"))
if(actualFile.getName.equals("nonexisting")) { // instead of null checking

}
else { // value of file was good

}

Fare cose del genere non mi sembra poi così "giusto".L'ho notato anche io .get è diventato deprecato..È questo il genere di cose che state facendo anche voi con Option o sto andando nella direzione sbagliata?

È stato utile?

Soluzione

Generalmente non è una buona idea tornare Option e poi utilizzare getOrElse per produrre un valore sentinella che significa "non trovato".Questo è ciò che Option è progettato per:per indicare che un valore non è stato trovato!

Option mostra davvero la sua potenza se usato insieme a costrutti di programmazione funzionale come map E foreach.Questo è più potente quando si ha a che fare con più opzioni.Ad esempio, supponiamo di scrivere un metodo che prende una stringa e mi restituisce un file, ma solo se il file esiste ed è un file e non una directory:

import java.io._;
def niceFile1(s: String): File = {
  val f = new File(s);
  if (f.exists && !f.isDirectory) f else null
}
def niceFile2(s: String): Option[File] = {
  val f = new File(s);
  if (f.exists && !f.isDirectory) Some(f) else None
}

Finora, utilizzando null è più facile, almeno finché non dimentichi che questo potrebbe darti null e ottieni una NPE.Comunque, ora proviamo ad usarlo.

def niceFopen1(s: String) = {
  val f = niceFile1(s);
  if (f!=null) new FileInputStream(f) else null;
}
def niceFopen2(s: String) = niceFile2(s).map(f => new FileInputStream(f))

Guarda cosa è successo!Nel primo caso, dovevamo eseguire manualmente i test logici e creare variabili temporanee.Uffa!Nel secondo caso, map ha fatto tutto il lavoro sporco per noi:None è stato mappato su None e Some(file) è stato mappato Some(fileinputstream).Facile!

Ma va ancora meglio.Forse vogliamo trovare la dimensione di un intero gruppo di file:

def totalSize2(ss: Seq[String]) = {
  (0L /: ss.flatMap(niceFile2)){(sum,f) => sum+f.length}
}

Aspetta, cosa sta succedendo qui... che dire di tutto... None?Non dobbiamo prestare attenzione e gestirli in qualche modo?Bene, ecco dove flatMap entra:unisce tutte le risposte in un unico elenco. None è una risposta di lunghezza zero, quindi la ignora. Some(f) ha una risposta...f--quindi lo inserisce nell'elenco.Quindi utilizziamo una piega per sommare tutte le lunghezze, ora che tutti gli elementi nell'elenco sono validi.Molto carino!

Altri suggerimenti

E 'una buona idea quella di non risolve il valore del Option, ma per applicare logica di tutto ciò che è in esso contenuti:

findFile.foreach(process(_))

In pratica questo elabora una File se uno si trova e non fa nulla in caso contrario (ed è equivalente a Thomas' prima comprensione for perché for compila per una chiamata a foreach). Si tratta di una versione più concisa:

findFile match {
  case Some(f) => process(f)
  case None =>
}

Cosa c'è di più, la cosa bella di questo è che si può catena operazioni, qualcosa come:

(findLiveFile orElse fileBackupFile orElse findTempFile).foreach(process(_)

Nella maggior parte dei casi si può usare il pattern matching

file match {
   case Some(f) => { .. } //file is there
   case _ => { .. } //file is not there 
}

Se siete interessati solo nel file, se è lì è possibile utilizzare una per l'espressione

for(f <- file) { //file is there 
}

È quindi possibile la catena per le espressioni di lavorare su più livelli su contenitori

for{ 
  option <- List(Some(1), None, Some(2))
  f <- option
} yield f

res0: List[Int] = List(1, 2)

In alternativa è possibile utilizzare TestDefinito e ottenere:

if(option.isDefined) {
   val x = option.get;
} else {
}

get non è deprecato in Scala 2.8.0.Beta-1.

Ecco un'alternativa:

var file:Option[File] = None
// ...
file map (new File(_)) foreach { fh =>
  // ...
}

Tuttavia, se avete bisogno di fare qualcosa se il file esiste, e qualcos'altro, se così non fosse, una dichiarazione match è più appropriato:

var file:Option[File] = None
// ...
file map (new File(_)) match {
  case Some(fh) =>
    // ...
  case None =>
    // ...
}

Questo è più o meno lo stesso di una dichiarazione if, ma mi piace è il concatenamento natura meglio per cose come Option, dove voglio estrarre un valore pure.

Per lo più ribadire ciò che tutti dicono, ma penso che ci sia almeno 4 diverse situazioni che si incontrano quando si ha un var annullabile come questo:

1. Il file non deve essere nullo

load(file.get())

Sarà un'eccezione quando si tenta di utilizzare effettivamente il file. Fail veloce, yay!

2. Il file è nullo, ma in questo caso abbiamo un default ragionevole:

load(file.getOrElse(new File("/defaultFile")))

3. Due rami di logica a seconda che il file esiste:

  file match {
    case Some(f) => { .. } //file is there
    case _ => { .. } //file is not there 
  }

4. No-op se il file è nullo, alias silenziosamente sicuro. Non credo che questo è molto spesso preferibile per me nella vita reale:

for (f <- file) {
//do some stuff
}

O, forse più chiaramente?

if (f.isDefined) {
  //do some stuff
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top