Respostas:
Usar Maybe(ou o primo Eitherque 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 indexOfretornaria um Maybevalor porque você espera a possibilidade de que o item não esteja na lista. É como retornar nullde uma função, exceto de uma maneira segura para o tipo, que o força a lidar com o nullcaso. Eitherfunciona 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 Eithercom uma que lança uma exceção. Para o caso de exceção, seu único recurso real é uma try...catchdeclaração. Para a Eitherfunçã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..catche, mais importante, mais semânticos. Usar uma função como <|>ou optionaltorna suas intenções muito mais claras do que usar try...catchpara 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 Maybee Eithercomo 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 Nothingum 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, throwsinstruçõ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/ Eithersã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, Maybenem Eithersubstitua 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 Maybevalor.
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/ Eitherporque acho mais simples e flexível.
OpenFile()pode jogar FileNotFoundou NoPermissionou TooManyDescriptorsetc. A Nenhum não carrega essas informações.if None return Noneinstruçõ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)
Nonevalores 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 .
bindde tal maneira que o teste Nonenão implique uma sobrecarga sintática no entanto. Um exemplo muito simples, o C # sobrecarrega os Nullableoperadores adequadamente. Nenhuma verificação é Nonenecessá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.
Maybecomo uma mônada é tornar a propagação Noneimplícita. Isso significa que, se você deseja retornar um Nonedado 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 Nonetipo de afirmação.
nullverificar 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 Maybecomo 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:
Eithermecanismo 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 Eitherestilo 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 NPEa 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.
Maybenúmeros escrevendo a + b sem a necessidade de verificar None, e o resultado é mais uma vez um valor opcional.
Maybe tipo , mas o uso Maybecomo 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.