Respostas:
Usar Maybe
(ou o primo Either
que funciona basicamente da mesma maneira, mas permite retornar um valor arbitrário no lugar de Nothing
) serve a um propósito ligeiramente diferente das exceções. Em termos de Java, é como ter uma exceção verificada em vez de uma exceção de tempo de execução. Representa algo esperado com o qual você precisa lidar, em vez de um erro que você não esperava.
Portanto, uma função como indexOf
retornaria um Maybe
valor porque você espera a possibilidade de que o item não esteja na lista. É como retornar null
de uma função, exceto de uma maneira segura para o tipo, que o força a lidar com o null
caso. Either
funciona da mesma maneira, exceto que você pode retornar informações associadas ao caso de erro; portanto, é realmente mais semelhante a uma exceção que Maybe
.
Então, quais são as vantagens da abordagem Maybe
/ Either
? Por um lado, é um cidadão de primeira classe do idioma. Vamos comparar uma função usando Either
com uma que lança uma exceção. Para o caso de exceção, seu único recurso real é uma try...catch
declaração. Para a Either
função, você pode usar os combinadores existentes para tornar o controle de fluxo mais claro. Aqui estão alguns exemplos:
Primeiro, digamos que você queira tentar várias funções que podem ter erros consecutivos até que você obtenha uma que não funcione. Se você não obtiver nenhum erro, deseje retornar uma mensagem de erro especial. Este é realmente um padrão muito útil, mas seria uma dor horrível try...catch
. Felizmente, como Either
é apenas um valor normal, você pode usar as funções existentes para tornar o código muito mais claro:
firstThing <|> secondThing <|> throwError (SomeError "error message")
Outro exemplo é ter uma função opcional. Digamos que você tenha várias funções para executar, incluindo uma que tenta otimizar uma consulta. Se isso falhar, você deseja que todo o resto seja executado. Você pode escrever um código como:
do a <- getA
b <- getB
optional (optimize query)
execute query a b
Ambos os casos são mais claros e mais curtos que o uso try..catch
e, mais importante, mais semânticos. Usar uma função como <|>
ou optional
torna suas intenções muito mais claras do que usar try...catch
para sempre lidar com exceções.
Observe também que você não precisa desarrumar seu código com linhas como if a == Nothing then Nothing else ...
! O objetivo principal de tratar Maybe
e Either
como mônada é evitar isso. Você pode codificar a semântica de propagação na função de ligação para obter as verificações de nulo / erro gratuitamente. O único momento em que você precisa checar explicitamente é se deseja retornar algo diferente de Nothing
um a Nothing
, e mesmo assim é fácil: existem várias funções de biblioteca padrão para tornar esse código mais agradável.
Finalmente, outra vantagem é que um tipo Maybe
/ Either
é apenas mais simples. Não há necessidade de estender o idioma com palavras-chave adicionais ou estruturas de controle - tudo é apenas uma biblioteca. Como são apenas valores normais, isso simplifica o sistema de tipos - em Java, é necessário diferenciar entre tipos (por exemplo, o tipo de retorno) e efeitos (por exemplo, throws
instruções) onde você não usaria Maybe
. Eles também se comportam como qualquer outro tipo definido pelo usuário - não é necessário ter um código especial de tratamento de erros inserido no idioma.
Outra vitória é que Maybe
/ Either
são functores e mônadas, o que significa que eles podem tirar proveito das funções de fluxo de controle de mônadas existentes (das quais existe um número razoável) e, em geral, jogam bem junto com outras mônadas.
Dito isto, existem algumas ressalvas. Por um lado, Maybe
nem Either
substitua exceções não verificadas . Você desejará alguma outra maneira de lidar com coisas como dividir por 0 simplesmente porque seria doloroso fazer com que cada divisão retornasse um Maybe
valor.
Outro problema é o retorno de vários tipos de erros (isso se aplica apenas a Either
). Com exceções, você pode lançar diferentes tipos de exceções na mesma função. com Either
, você obtém apenas um tipo. Isso pode ser superado com a sub-digitação ou um ADT contendo todos os diferentes tipos de erros como construtores (essa segunda abordagem é a que geralmente é usada no Haskell).
Ainda assim, prefiro a abordagem Maybe
/ Either
porque acho mais simples e flexível.
OpenFile()
pode jogar FileNotFound
ou NoPermission
ou TooManyDescriptors
etc. A Nenhum não carrega essas informações.if None return None
instruções de estilo.Mais importante ainda, uma exceção e uma mônada Talvez têm objetivos diferentes - uma exceção é usada para significar um problema, enquanto uma Talvez não.
"Enfermeira, se houver um paciente na sala 5, você pode pedir para ele esperar?"
(observe o "se" - isso significa que o médico está esperando uma mônada Talvez)
None
valores podem ser apenas propagados). Seu ponto 5 é apenas meio certo ... a questão é: quais situações são inequivocamente excepcionais? Como se vê ... não muitos .
bind
de tal maneira que o teste None
não implique uma sobrecarga sintática no entanto. Um exemplo muito simples, o C # sobrecarrega os Nullable
operadores adequadamente. Nenhuma verificação é None
necessária, mesmo ao usar o tipo. É claro que a verificação ainda está concluída (é do tipo seguro), mas nos bastidores e não atrapalha seu código. O mesmo se aplica, em certo sentido, à sua objeção à minha objeção a (5), mas concordo que nem sempre isso se aplica.
Maybe
como uma mônada é tornar a propagação None
implícita. Isso significa que, se você deseja retornar um None
dado None
, não precisa escrever nenhum código especial. O único momento em que você precisa corresponder é se deseja fazer algo especial None
. Você nunca precisa de um if None then None
tipo de afirmação.
null
verificar exatamente assim (por exemplo if Nothing then Nothing
) de graça, porque Maybe
é uma mônada. Está codificado na definição de bind ( >>=
) para Maybe
.
Either
) que se comportam da mesma maneira Maybe
. Alternar entre os dois é realmente bastante simples, porque Maybe
é realmente apenas um caso especial de Either
. (Em Haskell, você poderia pensar Maybe
como Either ()
.)
"Talvez" não substitui exceções. Exceções devem ser usadas em casos excepcionais (por exemplo: abrir uma conexão db e o servidor db não existe, embora deva existir). "Talvez" é para modelar uma situação em que você pode ou não ter um valor válido; digamos que você esteja obtendo um valor de um dicionário para uma chave: ele pode estar lá ou não - não há nada de "excepcional" em nenhum desses resultados.
Segundo a resposta de Tikhon, mas acho que há um ponto prático muito importante que todo mundo está perdendo:
Either
mecanismo não está acoplado a threads.Então, algo que estamos vendo hoje em dia na vida real é que muitas soluções de programação assíncronas estão adotando uma variante do Either
estilo de manipulação de erros. Considere as promessas de Javascript , conforme detalhado em qualquer um desses links:
O conceito de promessas permite que você escreva códigos assíncronos como este (extraídos do último link):
var greetingPromise = sayHello();
greetingPromise
.then(addExclamation)
.then(function (greeting) {
console.log(greeting); // 'hello world!!!!’
}, function(error) {
console.error('uh oh: ', error); // 'uh oh: something bad happened’
});
Basicamente, uma promessa é um objeto que:
Basicamente, como o suporte a exceções nativas do idioma não funciona quando a computação está acontecendo em vários segmentos, uma implementação promissora deve fornecer um mecanismo de tratamento de erros, e eles acabam sendo mônadas semelhantes aos tipos Maybe
/ de Haskell Either
.
O sistema do tipo Haskell exigirá que o usuário reconheça a possibilidade de a Nothing
, enquanto as linguagens de programação geralmente não exigem que uma exceção seja capturada. Isso significa que saberemos, em tempo de compilação, que o usuário verificou um erro.
throws NPE
a cada assinatura e catch(...) {throw ...}
a cada corpo de método. Mas acredito que há um mercado para checagem no mesmo sentido que em Talvez: a nulidade é opcional e rastreada no sistema de tipos.
Talvez a mônada seja basicamente o mesmo que o uso mais comum da linguagem da verificação de "nulo significa erro" (exceto que exige que o nulo seja verificado) e possui as mesmas vantagens e desvantagens.
Maybe
números escrevendo a + b
sem a necessidade de verificar None
, e o resultado é mais uma vez um valor opcional.
Maybe
tipo , mas o uso Maybe
como mônada adiciona açúcar de sintaxe que permite expressar a lógica de nulidade muito mais elegante.
O tratamento de exceções pode ser uma verdadeira dor de fatoração e teste. Eu sei que o python fornece boa sintaxe "com" que permite interceptar exceções sem o rígido bloco "try ... catch". Mas em Java, por exemplo, os blocos try catch são grandes, padronizados, detalhados ou extremamente detalhados e difíceis de quebrar. Além disso, o Java adiciona todo o ruído em torno das exceções verificadas versus não verificadas.
Se, em vez disso, sua mônada captura exceções e as trata como uma propriedade do espaço monádico (em vez de alguma anomalia de processamento), você pode misturar e combinar funções vinculadas a esse espaço, independentemente do que elas lançam ou capturam.
Se, melhor ainda, sua mônada evita condições em que exceções podem ocorrer (como enviar uma verificação nula para Maybe), é ainda melhor. se ... então é muito, muito mais fácil fatorar e testar do que tentar ... capturar.
Pelo que vi, Go está adotando uma abordagem semelhante, especificando que cada função retorna (resposta, erro). É o mesmo que "elevar" a função para um espaço de mônada, em que o tipo de resposta principal é decorado com uma indicação de erro e efetivamente lança exceções.