Utilizando a Opção em todo o lugar se sente um pouco estranho.Estou fazendo algo errado?
-
21-09-2019 - |
Pergunta
Como resultado dos artigos que li sobre o Option
classe que ajuda a evitar NullPointerException, eu comecei a usá-lo em todo o lugar.Imagine algo como isto:
var file:Option[File] = None
e mais tarde, quando eu usá-lo:
val actualFile = file.getOrElse(new File("nonexisting"))
if(actualFile.getName.equals("nonexisting")) { // instead of null checking
}
else { // value of file was good
}
Fazer coisas como esta não sentir todo esse "direito" para mim.Eu também notei que .get
tornou-se obsoleto..É este tipo de coisas que vocês estão fazendo com Opção é muito, ou eu estou indo para o caminho errado?
Solução
Geralmente não é uma boa idéia para retornar Option
e, em seguida, usar getOrElse
para produzir alguns sentinela valor que significa que "não encontrado".Que é o que Option
é projetado para:para indicar que um valor não é encontrado!
Option
mostra realmente o seu poder quando usado em conjunto com programação funcional construções como map
e foreach
.Este é mais potente ao lidar com várias opções.Por exemplo, suponhamos que eu escreva um método que recebe uma cadeia de caracteres e me dá de volta a um arquivo, mas somente se o arquivo existe e é um arquivo e não um diretório:
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
}
Até agora, usando null
é mais fácil-pelo menos até que você se esqueça que isso pode dar-lhe null
e você terá um NPE.De qualquer maneira, vamos agora tentar usá-lo.
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))
Veja o que aconteceu!No primeiro caso, tivemos que fazer testes de lógica pela mão e criar variáveis temporárias.Ugh!No segundo caso, map
fez todo o trabalho sujo para nós:Nenhum foi mapeada para Nenhum, e Some(file)
foi mapeado para Some(fileinputstream)
.Fácil!
Mas ele fica melhor ainda.Talvez nós queremos encontrar o tamanho de um monte de arquivos:
def totalSize2(ss: Seq[String]) = {
(0L /: ss.flatMap(niceFile2)){(sum,f) => sum+f.length}
}
Espere, o que está acontecendo aqui-o que sobre todas as None
?Não temos que prestar atenção e lidar com eles de alguma forma?Bem, isso é onde flatMap
vem:ele reúne todas as respostas em uma única lista. None
é uma resposta de comprimento zero, então ignora-lo. Some(f)
tem uma resposta--f
--então é coloca-lo na lista.Em seguida, usamos uma dobra para somar todos os comprimentos--agora que todos os elementos na lista são válidas.Muito bom!
Outras dicas
É uma boa ideia não resolver o valor do Option
, mas para Aplique lógica para o que for que ele contém:
findFile.foreach(process(_))
Basicamente, isso processa um File
Se alguém for encontrado e não faz nada de outra forma (e é equivalente ao primeiro for
compreensão porque for
compila com uma chamada para foreach
). É uma versão mais concisa de:
findFile match {
case Some(f) => process(f)
case None =>
}
Além disso, o melhor disso é que você pode corrente Operações, algo como:
(findLiveFile orElse fileBackupFile orElse findTempFile).foreach(process(_)
Na maioria dos casos, você usaria a correspondência de padrões
file match {
case Some(f) => { .. } //file is there
case _ => { .. } //file is not there
}
Se você está interessado apenas no arquivo, se estiver lá, poderá usar uma expressão
for(f <- file) { //file is there
}
Então você pode cadear para que as expressões funcionem em vários níveis em contêineres
for{
option <- List(Some(1), None, Some(2))
f <- option
} yield f
res0: List[Int] = List(1, 2)
Como alternativa, você pode usar o ISDefined e obter:
if(option.isDefined) {
val x = option.get;
} else {
}
O GET não está depreciado em Scala 2.8.0.beta-1.
Aqui está uma alternativa:
var file:Option[File] = None
// ...
file map (new File(_)) foreach { fh =>
// ...
}
No entanto, se você precisar fazer algo se o arquivo existir e outra coisa, se não acontecer, um match
A declaração é mais apropriada:
var file:Option[File] = None
// ...
file map (new File(_)) match {
case Some(fh) =>
// ...
case None =>
// ...
}
Isso é praticamente o mesmo que um if
declaração, mas eu gosto mais da natureza da natureza para coisas como Option
, onde eu quero extrair um valor também.
Reafirmando principalmente o que todo mundo está dizendo, mas acho que há pelo menos 4 situações diferentes que você encontrará quando tiver um Var anulável assim:
1. O arquivo não deve ser nulo
load(file.get())
Vai dar uma exceção ao tentar realmente usar o arquivo. Falha rápido, sim!
2. O arquivo é nulo, mas neste caso temos um padrão sensato:
load(file.getOrElse(new File("/defaultFile")))
3. Dois ramos da lógica com base no existe o arquivo:
file match {
case Some(f) => { .. } //file is there
case _ => { .. } //file is not there
}
4. NÃO-OP Se o arquivo for nulo, também conhecido como silenciosamente falhar. Eu não acho que este seja frequentemente preferível para mim na vida real:
for (f <- file) {
//do some stuff
}
Ou, talvez mais claramente?
if (f.isDefined) {
//do some stuff
}