Pergunta

Eu não sou capaz de entender o ponto de Option[T] classe em Scala.Quero dizer, eu não sou capaz de ver qualquer advanages de None mais null.

Por exemplo, considere o código:

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 */
    }
  }
}

Agora, suponhamos que o método getPerson1 retorna null, e , em seguida, a chamada feita para display na primeira linha de main está fadado ao fracasso com NPE.Da mesma forma, se getPerson2 retorna None, o display chamada novamente falhar com algum erro semelhante.

Se é assim, então por que não Scala complicar as coisas através da introdução de um novo valor de wrapper (Option[T]) em vez de seguir uma abordagem simples, usado em Java?

ATUALIZAÇÃO:

Eu editei o meu código como por @Mitch's sugestão.Eu não sou ainda capaz de ver qualquer vantagem particular de Option[T].Eu tenho que testar para o excepcional null ou None em ambos os casos.:(

Se eu entendi corretamente, a partir de @Michael responder, é a única vantagem de Option[T] é que ele explicitamente diz o programador que este método pode retornar Nenhum?É esta a única razão por trás desta escolha de design?

Foi útil?

Solução

Você vai entender o ponto de Option melhor se você se forçar a nunca, nunca, use get. Isso é porque get é o equivalente a "OK, envie-me de volta à terra nula".

Então, tome esse exemplo seu. Como você ligaria display sem utilizar get? Aqui estão algumas alternativas:

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

Nenhuma dessas alternativas deixará você ligar display em algo que não existe.

Quanto ao porquê get Existe, Scala não diz como seu código deve ser escrito. Pode incorporá -lo gentilmente, mas se você quiser voltar à rede de segurança, é sua escolha.


Você acertou aqui:

A única vantagem da opção [T] é explicitamente diz ao programador que esse método não pode retornar nenhum?

Exceto pelo "único". Mas deixe -me reafirmar isso de outra maneira: o a Principal vantagem de Option[T] sobre T é o tipo de segurança. Ele garante que você não enviará um T Método para um objeto que pode não existir, pois o compilador não permite.

Você disse que precisa testar a anulação nos dois casos, mas se você esquecer - ou não sabe - precisa verificar se o compilador lhe dirá? Ou seus usuários vão?

Obviamente, devido à sua interoperabilidade com o Java, o Scala permite nulos, assim como Java. Portanto, se você usar bibliotecas Java, se você usar bibliotecas de Scala mal escritas ou se você usar mal escritas pessoal Bibliotecas de Scala, você ainda terá que lidar com indicadores nulos.

Outras duas vantagens importantes de Option Eu posso pensar em:

  • Documentação: Uma assinatura do tipo de método informará se um objeto é sempre retornado ou não.

  • Composibilidade monádica.

O último leva muito mais tempo para apreciar completamente, e não é adequado para exemplos simples, pois mostra apenas sua força no código complexo. Então, darei um exemplo abaixo, mas estou ciente de que quase não significará qualquer coisa, exceto as pessoas que já o entendem.

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

Outras dicas

Comparar:

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

com:

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

A propriedade Monadic ligar, que aparece em Scala como o mapa A função nos permite encadear operações em objetos sem se preocupar se eles são 'nulos' ou não.

Leve este exemplo simples um pouco mais longe. Digamos que queríamos encontrar todas as cores favoritas de uma lista de pessoas.

// 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

Ou talvez gostássemos de encontrar o nome da irmã da mãe de uma pessoa:

// 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)

Espero que isso esclareça como as opções podem tornar a vida um pouco mais fácil.

A diferença é sutil. Lembre -se de ser realmente uma função devo retornar um valor - nulo não é realmente considerado um "valor normal de retorno" nesse sentido, mais um Tipo inferior/nada.

Mas, em um sentido prático, quando você chama uma função que opcionalmente retorna algo, você faria:

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

É verdade que você pode fazer algo semelhante com NULL - mas isso faz a semântica de ligar getPerson2 óbvio em virtude do fato de que ele retorna Option[Person] (Uma coisa prática agradável, além de confiar em alguém lendo o médico e obter um NPE porque não leu o médico).

Vou tentar desenterrar um programador funcional que possa dar uma resposta mais rigorosa do que eu.

Para mim, as opções são realmente interessantes quando tratadas com a sintaxe da compreensão. Tirando Synesso Exemplo anterior:

// 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 alguma da atribuição for None, a fathersMothersSister vai ser None mas não NullPointerException será levantado. Você pode passar com segurança fathersMothersSisterpara uma função que assume parâmetros de opção sem se preocupar. Então você não verifica o NULL e não se importa com exceções. Compare isso com a versão Java apresentada em Synesso exemplo.

Você tem recursos de composição bastante poderosos com a opção:

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") )

Talvez outra pessoa apontou isso, mas eu não vi:

Uma vantagem de correspondência de padrões com a opção [t] vs. verificação nula é que a opção é uma classe selada; portanto, o compilador Scala emitirá um aviso se você deixar de codificar algum caso ou nenhum caso. Há um sinalizador do compilador no compilador que transformará avisos em erros. Portanto, é possível impedir a falha em lidar com o caso "não existe" no momento da compilação, e não no tempo de execução. Essa é uma vantagem enorme sobre o uso do valor nulo.

Não está lá para ajudar a evitar uma verificação nula, está lá para forçar uma verificação nula. O ponto fica claro quando sua classe tem 10 campos, dois dos quais podem ser nulos. E seu sistema possui outras 50 classes semelhantes. No mundo Java, você tenta evitar NPEs nesses campos usando alguma combinação de horepower mental, convenção de nomeação ou talvez até anotações. E todo Java Dev falha nisso em um grau significativo. A classe de opções não apenas deixa os valores "anuláveis" visualmente claros para qualquer desenvolvedor que tenta entender o código, mas permite ao compilador fazer cumprir esse contrato anteriormente não dito.

[ copiado a partir de este comentário por Daniel Spiewak ]

Se a única maneira de usar Option foram a correspondência de padrões, a fim de obter valores de, então sim, eu concordo que é não melhorar em todos nulo.No entanto, falta um *enorme* classe a sua funcionalidade.A única razão convincente para usar Option é se você estiver usando o seu de ordem superior funções de utilidade.Efetivamente, você precisa estar usando a sua monádico natureza.Por exemplo (assumindo que uma certa quantidade de API de corte):

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

Não, não era bacana?Podemos na verdade, fazer muito melhor de se usar for-compreensões:

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

Você vai perceber que estamos *nunca* verificar explicitamente null, Nenhum ou qualquer um dos seus adjuntos.O ponto inteiro de Opção é evitar qualquer que verificação.Você acabou de seqüência de cálculos ao longo e mover para baixo a linha até você ** realmente precisa para obter um valor limite.No de que ponto, você pode decidir se quer ou não é você deseja fazer a verificação explícita (o que você deve nunca tem que fazer), fornecer um valor padrão, jogue uma de exceção, etc.

Eu nunca, nunca fazer qualquer explícito de correspondência contra Option, e eu conheço um monte de outros Scala desenvolvedores que estão no mesmo barco.David Pollak mencionados para - me outro dia que ele usa tais explícita de correspondência em Option (ou Box, no caso do Elevador) como um sinal que o programador que escreveu o código totalmente não entender o idioma e a sua biblioteca padrão.

Eu não quero ser um troll martelo, mas você realmente precisa de olhar para a forma recursos de linguagem são *realmente* usado na prática, antes de batê-los como inúteis.Estou absolutamente de acordo que A opção é bastante uncompelling como *você* usado, mas não o estiver a utilizar o forma como foi concebido.

Um ponto que ninguém mais aqui parece ter levantado é que, embora você possa ter uma referência nula, há uma distinção introduzida pela opção.

Isto é, você pode ter Option[Option[A]], que seria habitado por None, Some(None) e Some(Some(a)) Onde a é um dos habitantes usuais de A. Isso significa que, se você tiver algum tipo de contêiner e deseja poder armazenar indicadores nulos e tirá -los, precisará repassar algum valor booleano extra para saber se você realmente obteve um valor. Verrugas assim abundante Nas APIs de contêineres Java e algumas variantes livres de trava não conseguem nem fornecer.

null é uma construção única, não se comporá consigo mesmo, está disponível apenas para tipos de referência e força você a raciocinar de maneira não total.

Por exemplo, quando você verifica

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

você tem que carregar em sua cabeça por toda a else ramificar isso x != null E que isso já foi verificado. No entanto, ao usar algo como opção

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

vocês conhecer y não é Nonepor construção - e você saberia que não era null Também se não fosse por Hoare's erro de bilhões de dólares.

A opção [T] é uma mônada, que é realmente útil quando você usa funções de alta ordem para manipular valores.

Sugiro que você leia artigos listados abaixo, eles são realmente bons artigos que mostram por que a opção [t] é útil e como pode ser usada de maneira funcional.

Adicionando ao Randall's Teaser de uma resposta, entender por que a potencial ausência de um valor é representada por Option requer entender o quê Option compartilha com muitos outros tipos em Scala - especificamente, tipos de modelagem de Monads. Se alguém representa a ausência de um valor com NULL, essa distinção de ausência de presença não poderá participar dos contratos compartilhados pelos outros tipos de Monadic.

Se você não sabe o que são mônadas, ou se você não percebe como eles estão representados na biblioteca de Scala, você não verá o que Option Joga junto com, e você não pode ver o que está perdendo. Existem muitos benefícios em usar Option Em vez de nulo, isso seria digno de nota, mesmo na ausência de qualquer conceito de Monad (discuto alguns deles no "custo da opção / alguns vs nulos" Scala-User Tópico da lista de correspondência aqui), mas falar sobre isso isolamento é como falar sobre um tipo de iterador de implementação de lista vinculada, imaginando por que é necessário, o tempo todo perdido na interface mais geral de contêineres/iterador/algoritmo. Há uma interface mais ampla no trabalho aqui também, e Option Fornece um modelo de presença e ausência dessa interface.

Eu acho que a chave é encontrada na resposta de Synesso: a opção é não Principalmente útil como um pseudônimo com nulo, mas como um objeto completo que pode ajudá-lo com sua lógica.

O problema com nulo é que é o falta de um objeto. Ele não possui métodos que possam ajudá -lo a lidar com isso (embora, como designer de idiomas, você possa adicionar listas cada vez mais longas de recursos ao seu idioma que imitam um objeto, se você realmente se sentir assim).

Uma opção pode fazer, como você demonstrou, é emular nulo; Você então precisa testar o valor extraordinário "nenhum" em vez do valor extraordinário "nulo". Se você esquecer, em ambos os casos, coisas ruins acontecerão. A opção torna menos provável que aconteça por acidente, já que você precisa digitar "Get" (o que deve lembrá -lo que isso pode ser nulo, er, quero dizer nenhum), mas este é um pequeno benefício em troca de um objeto de wrapper extra.

Onde a opção realmente começa a mostrar seu poder está ajudando você a lidar com o conceito de que eu me desenhava algo, mas-eu não-na verdade,--um.

Vamos considerar algumas coisas que você pode querer fazer com coisas que podem ser nulas.

Talvez você queira definir um valor padrão se tiver um nulo. Vamos comparar Java e Scala:

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

No lugar de um um tanto pesado ?: Construct, temos um método que lida com a idéia de "usar um valor padrão se eu for nulo". Isso limpa um pouco o seu código.

Talvez você queira criar um novo objeto apenas se tiver um valor real. Comparar:

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

O Scala é um pouco mais curto e novamente evita fontes de erro. Em seguida, considere o benefício cumulativo quando precisar encadear as coisas, como mostrado nos exemplos de Synesso, Daniel e paradigmático.

Não é um grande Melhoria, mas se você adicionar tudo, vale a pena em todos os lugares, economize um código de alto desempenho (onde deseja evitar até a pequena sobrecarga de criar o objeto de algum (x) wrapper).

O uso da correspondência não é realmente tão útil por si só, exceto como um dispositivo para alertá -lo sobre o caso NULL/Nenhum. Quando é realmente útil é quando você começa a encaderá -lo, por exemplo, se você tiver uma lista de opções:

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.")
}

Agora você pode dobrar os casos nenhum e os casos de Lista e Is vazios, todos juntos, em uma declaração útil que retira exatamente o valor que você deseja.

Os valores de retorno nulo estão presentes apenas para compatibilidade com Java. Você não deve usá -los de outra forma.

É realmente uma questão de estilo de programação. Usando Java funcional ou escrevendo seus próprios métodos auxiliares, você pode ter sua funcionalidade de opção, mas não abandonar o idioma Java:

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

Só porque o Scala inclui por padrão não o torna especial. A maioria dos aspectos dos idiomas funcionais está disponível nessa biblioteca e pode coexistir bem com outro código Java. Assim como você pode optar por programar Scala com nulos, você pode optar por programar o Java sem eles.

Admitindo com antecedência que é uma resposta glib, a opção é uma mônada.

Na verdade eu compartilhar a dúvida com você.Sobre a Opção que realmente me incomoda que: 1) há uma sobrecarga de desempenho, como há uma lor de "Alguns" wrappers criado everywehre.2) eu tenho que usar um monte de Alguns e Opção no meu código.

Então, para ver as vantagens e desvantagens deste tipo de linguagem de design decisão devemos levar em consideração alternativas.Como Java simplesmente ignora o problema da nulidade, não é uma alternativa.A real alternativa fornece Fantom linguagem de programação.Há anulável e não nulo tipos de lá e ?.?:operadores em vez de Scala mapa/flatMap/getOrElse.Eu vejo o seguinte balas na comparação:

Opção de vantagem:

  1. uma linguagem mais simples - sem adicional de construções de linguagem necessárias
  2. uniforme com outros tipos monádico

Anulável vantagem:

  1. menor de sintaxe em casos típicos
  2. o melhor desempenho (como você não é necessário criar uma nova Opção de objetos e lambdas para o mapa, flatMap)

Portanto, não há vencedor óbvio aqui.E mais uma observação.Não há principais sintática vantagem para o uso de Opção.Você pode definir algo como:

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

Ou usar algumas conversões implícitas para obter pritty sintaxe com pontos.

A verdadeira vantagem de ter tipos de opções explícitos é que você é capaz de não Use -os em 98% de todos os lugares e, portanto, exclui estaticamente exceções nulas. (E nos outros 2%, o sistema de tipos lembra você para verificar corretamente quando você realmente os acessa.)

Outra situação em que a opção funciona, está em situações em que os tipos não conseguem ter um valor nulo. Não é possível armazenar nulo em um INT, flutuar, dobrar, etc., mas com uma opção, você pode usar o Nenhum.

Em Java, você precisaria usar as versões em caixas (número inteiro, ...) desses tipos.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top