Utilizando a Opção em todo o lugar se sente um pouco estranho.Estou fazendo algo errado?

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

  •  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?

Foi útil?

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
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top