Usare Option ovunque sembra un po' imbarazzante.Sto facendo qualcosa di sbagliato?
-
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?
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
}