Eu tenho lido sobre a (des) conveniência de ter em null
vez de (por exemplo) Maybe
. Depois de ler este artigo , estou convencido de que seria muito melhor usarMaybe
(ou algo semelhante). No entanto, fico surpreso ao ver que todas as linguagens de programação imperativas ou orientadas a objetos "conhecidas" ainda usam null
(o que permite acesso não verificado a tipos que podem representar um valor "nada"), e isso Maybe
é usado principalmente em linguagens de programação funcionais.
Como exemplo, observe o seguinte código C #:
void doSomething(string username)
{
// Check that username is not null
// Do something
}
Algo cheira mal aqui ... Por que deveríamos verificar se o argumento é nulo? Não devemos assumir que toda variável contém uma referência a um objeto? Como você pode ver, o problema é que, por definição, quase todas as variáveis podem conter uma referência nula. E se pudéssemos decidir quais variáveis são "anuláveis" e quais não? Isso nos pouparia muito esforço ao depurar e procurar uma "NullReferenceException". Imagine que, por padrão, nenhum tipo possa conter uma referência nula . Em vez disso, você declararia explicitamente que uma variável pode conter uma referência nula , apenas se você realmente precisar dela. Essa é a ideia por trás do Talvez. Se você possui uma função que, em alguns casos, falha (por exemplo, divisão por zero), você pode retornar umMaybe<int>
, declarando explicitamente que o resultado pode ser um int, mas também nada! Esse é um dos motivos para preferir Talvez, em vez de nulo. Se você estiver interessado em mais exemplos, sugiro ler este artigo .
Os fatos são que, apesar das desvantagens de tornar a maioria dos tipos anuláveis por padrão, a maioria das linguagens de programação OO realmente o faz. É por isso que me pergunto:
- Que tipo de argumentos você teria que implementar
null
em sua linguagem de programação em vez deMaybe
? Existem razões ou é apenas "bagagem histórica"?
Certifique-se de entender a diferença entre nulo e Talvez antes de responder a esta pergunta.
null
ou o seu conceito não existem (IIRC Haskell é um exemplo).null
s por um longo tempo. Não é fácil simplesmente largar isso.Respostas:
Eu acredito que é principalmente bagagem histórica.
O idioma mais importante e mais antigo com
null
é C e C ++. Mas aqui,null
faz sentido. Os ponteiros ainda são um conceito bastante numérico e de baixo nível. E como alguém disse, na mentalidade dos programadores de C e C ++, ter que dizer explicitamente que o ponteiro pode sernull
não faz sentido.O segundo da fila vem o Java. Considerando que os desenvolvedores de Java estavam tentando se aproximar do C ++, para que pudessem simplificar a transição do C ++ para o Java, provavelmente não queriam mexer com esse conceito principal da linguagem. Além disso, a implementação explícita
null
exigiria muito mais esforço, porque você deve verificar se a referência não nula está realmente definida corretamente após a inicialização.Todos os outros idiomas são iguais ao Java. Eles geralmente copiam da maneira que C ++ ou Java o fazem e, considerando o conceito principal
null
de tipos de referência implícitos , torna-se muito difícil projetar uma linguagem que esteja usando explícitanull
.fonte
null
não seria uma grande mudança, eu acho.std::string
não pode sernull
.int&
não pode sernull
. Umaint*
lata e o C ++ permitem acesso desmarcado por duas razões: 1. porque C fez e 2. porque você deveria entender o que está fazendo ao usar ponteiros brutos em C ++.Na verdade,
null
é uma ótima idéia. Dado um ponteiro, queremos designar que esse ponteiro não faz referência a um valor válido. Portanto, pegamos um local da memória, declaramos inválido e mantemos a convenção (uma convenção às vezes aplicada com segfaults). Agora, sempre que tenho um ponteiro, posso verificar se ele contémNothing
(ptr == null
) ouSome(value)
(ptr != null
,value = *ptr
). Eu quero que você entenda que isso é equivalente a umMaybe
tipo.Os problemas com isso são:
Em muitos idiomas, o sistema de tipos não ajuda aqui para garantir uma referência não nula.
Essa é uma bagagem histórica, pois muitas linguagens imperativas ou OOP convencionais tiveram apenas avanços incrementais em seus sistemas de tipos quando comparadas aos antecessores. Pequenas mudanças têm a vantagem de aprender novos idiomas. C # é uma linguagem convencional que introduziu ferramentas no nível da linguagem para lidar melhor com nulos.
Os designers de API podem retornar
null
com falha, mas não uma referência à coisa real em questão. Freqüentemente, a coisa (sem referência) é retornada diretamente. Esse nivelamento de um nível de ponteiro torna impossível usarnull
como valor.Isso é apenas preguiça do lado do designer e não pode ser ajudado sem impor o aninhamento adequado com um sistema de tipos adequado. Algumas pessoas também podem tentar justificar isso com considerações de desempenho ou com a existência de verificações opcionais (uma coleção pode retornar
null
ou o próprio item, mas também fornece umcontains
método).Em Haskell, há uma visão clara do
Maybe
tipo como mônada. Isso facilita a composição de transformações no valor contido.Por outro lado, idiomas de baixo nível como C mal tratam matrizes como um tipo separado, então não tenho certeza do que estamos esperando. Em linguagens OOP com polimorfismo parametrizado, um
Maybe
tipo verificado em tempo de execução é bastante trivial de implementar.fonte
null
menos do que no passado?null
s ” - estou falando sobreNullable
tipos e o??
operador padrão. No momento, ele não resolve o problema na presença de código legado, mas é um passo em direção a um futuro melhor.Nullable
.Meu entendimento é que essa
null
era uma construção necessária para abstrair as linguagens de programação do assembly. 1 Os programadores precisavam da capacidade de indicar que um valor de ponteiro ou registro eranot a valid value
enull
se tornou o termo comum para esse significado.Reforçando o ponto que
null
é apenas uma convenção para representar um conceito, o valor realnull
usado para poder / pode variar com base na linguagem e plataforma de programação.Se você estava projetando um novo idioma e queria evitar,
null
mas usar emmaybe
vez disso, incentivaria um termo mais descritivo, comonot a valid value
ounavv
para indicar a intenção. Mas o nome desse não valor é um conceito separado do fato de você permitir que não existam valores no seu idioma.Antes de decidir sobre um desses dois pontos, é necessário definir o significado de
maybe
seu sistema. Você pode achar que é apenas uma renomeação donull
significado denot a valid value
ou pode ter uma semântica diferente para o seu idioma.Da mesma forma, a decisão de verificar o acesso contra
null
acesso ou referência é outra decisão de design do seu idioma.Para fornecer um pouco de história,
C
havia uma suposição implícita de que os programadores entendiam o que estavam tentando fazer ao manipular a memória. Como era uma abstração superior à montagem e às linguagens imperativas que a precederam, arriscaria que o pensamento de proteger o programador de uma referência incorreta não tivesse passado pela cabeça deles.Acredito que alguns compiladores ou suas ferramentas adicionais podem fornecer uma medida de verificação em relação ao acesso inválido ao ponteiro. Portanto, outros observaram esse problema em potencial e tomaram medidas para se proteger.
Se você deve ou não permitir isso, depende do que você deseja que seu idioma cumpra e de qual grau de responsabilidade você deseja atribuir aos usuários do seu idioma. Também depende da sua capacidade de criar um compilador para restringir esse tipo de comportamento.
Então, para responder às suas perguntas:
"Que tipo de argumentos ..." - Bem, depende do que você deseja que o idioma faça. Se você deseja simular o acesso bare-metal, convém permitir.
"é apenas bagagem histórica?" Talvez não.
null
certamente teve / tem significado para vários idiomas e ajuda a direcionar a expressão desses idiomas. O precedente histórico pode ter afetado os idiomas mais recentes e sua permissão,null
mas é um pouco demais para agitar as mãos e declarar o conceito de bagagem histórica inútil.1 Consulte este artigo da Wikipedia, embora seja dado crédito ao Hoare por valores nulos e linguagens orientadas a objetos. Acredito que as linguagens imperativas progrediram em uma árvore genealógica diferente da de Algol.
fonte
null
).object o = null; o.ToString();
compila perfeitamente para mim, sem erros ou avisos no VS2012. O ReSharper se queixa disso, mas esse não é o compilador.Se você observar os exemplos no artigo que você citou, na maioria das vezes o uso
Maybe
não diminui o código. Não evita a necessidade de verificarNothing
. A única diferença é que você deve fazer isso através do sistema de tipos.Note, eu digo "lembre", não force. Programadores são preguiçosos. Se um programador está convencido de que um valor não pode ser
Nothing
, eles vão desreferenciar oMaybe
sem checá-lo, assim como desreferenciam um ponteiro nulo agora. O resultado final é que você converte uma exceção de ponteiro nulo em uma exceção "talvez vazio não referenciado".O mesmo princípio da natureza humana se aplica a outras áreas em que as linguagens de programação tentam forçar os programadores a fazer alguma coisa. Por exemplo, os designers de Java tentaram forçar as pessoas a lidar com a maioria das exceções, o que resultou em muitos clichês que ignoram silenciosamente ou propagam cegamente exceções.
O que
Maybe
é bom é quando muitas decisões são tomadas por meio de correspondência de padrões e polimorfismo em vez de verificações explícitas. Por exemplo, você pode criar funções separadasprocessData(Some<T>)
e com asprocessData(Nothing<T>)
quais não pode fazernull
. Você move automaticamente o tratamento de erros para uma função separada, o que é muito desejável na programação funcional em que as funções são passadas e avaliadas preguiçosamente, em vez de sempre serem chamadas de cima para baixo. No POO, a maneira preferida de desacoplar seu código de tratamento de erros é com exceções.fonte
const
, mas torná-lo opcional. Alguns tipos de código de nível inferior, como listas vinculadas, por exemplo, seriam muito irritantes para implementar com objetos não nulos.map :: Maybe a -> (a -> b)
ebind :: Maybe a -> (a -> Maybe b)
definido sobre ele, para que possa continuar e linha mais cálculos para a maior parte com a reformulação utilizando uma instrução else if. EgetValueOrDefault :: Maybe a -> (() -> a) -> a
permite que você lide com o caso anulável. É muito mais elegante do que a correspondência de padrõesMaybe a
explicitamente.Maybe
é uma maneira muito funcional de pensar em um problema - existe uma coisa e ela pode ou não ter um valor definido. No sentido orientado a objetos, no entanto, substituímos essa idéia de uma coisa (não importa se ela tem ou não valor) por um objeto. Claramente, um objeto tem um valor. Caso contrário, dizemos que o objeto énull
, mas o que realmente queremos dizer é que não há nenhum objeto. A referência que temos ao objeto não aponta para nada. TraduzirMaybe
para um conceito de OO não faz nada de novo - na verdade, apenas cria um código muito mais confuso. Você ainda precisa ter uma referência nula para o valor doMaybe<T>
. Você ainda precisa fazer verificações nulas (na verdade, é necessário fazer muito mais verificações nulas, sobrecarregando seu código), mesmo que agora elas sejam chamadas de "talvez verificações". Certamente, você escreverá um código mais robusto, como afirma o autor, mas eu diria que esse é o caso porque você tornou a linguagem muito mais abstrata e obtusa, exigindo um nível de trabalho desnecessário na maioria dos casos. De bom grado, eu aceitava uma NullReferenceException de vez em quando do que lidava com o código espaguete fazendo uma verificação Talvez sempre que eu acessava uma nova variável.fonte
Maybe<T>
tem que permitirnull
como um valor, porque o valor pode não existir. Se eu tiverMaybe<MyClass>
, e ele não tiver um valor, o campo value deverá conter uma referência nula. Não há mais nada que seja comprovadamente seguro.Maybe
poderia ser uma classe abstrata, com duas classes que herdam dela:Some
(que possui um campo para o valor) eNone
(que não possui esse campo). Dessa forma, o valor nunca énull
.O conceito de
null
pode ser facilmente rastreado até C, mas não é aí que está o problema.Minha linguagem cotidiana de escolha é C # e eu continuaria
null
com uma diferença. O C # possui dois tipos de tipos, valores e referências . Os valores nunca podem sernull
, mas há momentos em que eu gostaria de expressar que nenhum valor é perfeitamente adequado. Para fazer isso, o C # usaNullable
tipos, assimint
como o valor eint?
o valor anulável. É assim que acho que os tipos de referência também devem funcionar.Veja também: Referência nula pode não ser um erro :
fonte
Eu acho que isso ocorre porque a programação funcional está muito preocupada com tipos , especialmente tipos que combinam outros tipos (tuplas, funções como tipos de primeira classe, mônadas etc.) do que a programação orientada a objetos (ou pelo menos inicialmente).
Versões modernas das linguagens de programação das quais você está falando (C ++, C #, Java) são todas baseadas em linguagens que não tinham nenhuma forma de programação genérica (C, C # 1.0, Java 1). Sem isso, você ainda pode fazer algum tipo de diferença entre objetos anuláveis e não anuláveis na linguagem (como referências em C ++, que não podem ser
null
, mas também são limitadas), mas é muito menos natural.fonte
Eu acho que a razão fundamental é que relativamente poucas verificações nulas são necessárias para tornar um programa "seguro" contra corrupção de dados. Se um programa tentar usar o conteúdo de um elemento da matriz ou outro local de armazenamento que supostamente foi gravado com uma referência válida, mas não foi, o melhor resultado é que uma exceção seja lançada. Idealmente, a exceção indicará exatamente onde o problema ocorreu, mas o que importa é que algum tipo de exceção seja lançada antes que a referência nula seja armazenada em algum lugar que possa causar corrupção de dados. A menos que um método armazene um objeto sem tentar usá-lo de alguma maneira, uma tentativa de usar um objeto constituirá, por si só, uma espécie de "verificação nula".
Se alguém quiser garantir que uma referência nula que apareça onde não deveria causar uma exceção específica diferente
NullReferenceException
, muitas vezes será necessário incluir verificações nulas em todo o lugar. Por outro lado, apenas garantir que alguma exceção ocorra antes que uma referência nula possa causar "danos" além de qualquer que já tenha sido feito geralmente requer relativamente poucos testes - o teste geralmente seria necessário apenas nos casos em que um objeto armazenaria um objeto. referência sem tentar usá-lo, e a referência nula sobrescreveria uma válida ou faria com que outro código interpretasse mal outros aspectos do estado do programa. Tais situações existem, mas não são tão comuns; a maioria das referências nulas acidentais será capturada muito rapidamentese alguém procura por eles ou não .fonte
"
Maybe
," como está escrito, é uma construção de nível superior a nula. Usando mais palavras para defini-lo, talvez seja "um ponteiro para uma coisa ou um ponteiro para nada, mas o compilador ainda não recebeu informações suficientes para determinar qual". Isso obriga a verificar explicitamente cada valor constantemente, a menos que você construa uma especificação de compilador suficientemente inteligente para acompanhar o código que você escreve.Você pode fazer uma implementação de Maybe com uma linguagem que tenha nulos facilmente. C ++ possui um na forma de
boost::optional<T>
. Tornar o equivalente a null com Maybe é muito difícil. Em particular, se eu tiver umMaybe<Just<T>>
, não posso atribuí-lo a nulo (porque esse conceito não existe), enquanto aT**
em uma linguagem com nulo é muito fácil de atribuir a nulo. Isso força a pessoa a usarMaybe<Maybe<T>>
, que é totalmente válida, mas forçará você a fazer muito mais verificações para usar esse objeto.Algumas linguagens funcionais usam Talvez porque nulo exija comportamento indefinido ou manipulação de exceções, nenhum dos quais é um conceito fácil de mapear para sintaxes da linguagem funcional. Talvez preencha o papel muito melhor nessas situações funcionais, mas nas linguagens processuais, nulo é o rei. Não é uma questão de certo e errado, apenas uma questão do que torna mais fácil dizer ao computador para fazer o que você deseja.
fonte