Alguns idiomas afirmam não ter "nenhuma exceção de tempo de execução" como uma clara vantagem sobre outros idiomas que os possuem.
Estou confuso sobre o assunto.
A exceção de tempo de execução é apenas uma ferramenta, tanto quanto eu sei, e quando usada bem:
- você pode comunicar estados "sujos" (lançando dados inesperados)
- adicionando pilha você pode apontar para a cadeia de erro
- você pode distinguir entre desorganização (por exemplo, retornando um valor vazio em entrada inválida) e uso inseguro que requer atenção de um desenvolvedor (por exemplo, lançando exceção em entrada inválida)
- você pode adicionar detalhes ao seu erro com a mensagem de exceção, fornecendo mais detalhes úteis, ajudando os esforços de depuração (teoricamente)
Por outro lado, acho muito difícil depurar um software que "engole" exceções. Por exemplo
try {
myFailingCode();
} catch {
// no logs, no crashes, just a dirty state
}
Portanto, a questão é: qual é a forte vantagem teórica de "não haver exceções em tempo de execução"?
Exemplo
Sem erros de tempo de execução na prática. Nenhum nulo. Nenhum indefinido não é uma função.
Respostas:
Exceções têm semântica extremamente limitadora. Eles devem ser tratados exatamente onde são lançados, ou na pilha de chamadas diretas para cima, e não há indicação para o programador em tempo de compilação, se você esquecer de fazê-lo.
Compare isso com Elm, onde os erros são codificados como Results ou Maybes , que são os dois valores . Isso significa que você recebe um erro do compilador se não o manipular. Você pode armazená-los em uma variável ou mesmo em uma coleção para adiar o manuseio para um horário conveniente. Você pode criar uma função para manipular os erros de uma maneira específica do aplicativo, em vez de repetir blocos try-catch muito semelhantes em todo o lugar. Você pode encadeá-los em um cálculo que tenha êxito apenas se todas as partes tiverem êxito e elas não precisarão ser amontoadas em um bloco de tentativa. Você não está limitado pela sintaxe interna.
Isso não é nada como "exceções de deglutição". Está explicitando as condições de erro no sistema de tipos e fornecendo semânticas alternativas muito mais flexíveis para lidar com elas.
Considere o seguinte exemplo. Você pode colar isso em http://elm-lang.org/try se desejar vê-lo em ação.
Observe o
String.toInt
nacalculate
função tem a possibilidade de falhar. Em Java, isso pode gerar uma exceção de tempo de execução. Enquanto lê a entrada do usuário, há uma chance bastante boa disso. Elm, em vez disso, me obriga a lidar com isso retornando umResult
, mas observe que não preciso lidar com isso imediatamente. Eu posso duplicar a entrada e convertê-la em uma string e, em seguida , verificar se há entrada incorreta nagetDefault
função. Esse local é muito mais adequado para a verificação do que o ponto em que ocorreu o erro ou mais na pilha de chamadas.A maneira como o compilador força nossa mão também é muito mais refinada do que as exceções verificadas do Java. Você precisa usar uma função muito específica, como
Result.withDefault
para extrair o valor que deseja. Embora tecnicamente você possa abusar desse tipo de mecanismo, não há muito sentido. Como você pode adiar a decisão até saber uma boa mensagem de erro / padrão a ser colocada, não há motivo para não usá-la.fonte
That means you get a compiler error if you don't handle the error.
- Bem, esse foi o raciocínio por trás das exceções verificadas em Java, mas todos sabemos o quanto isso funcionou.Maybe
,Either
, etc. Elm parece que está tomando uma página de linguagens como ML, OCaml ou Haskell.x = some_func()
, não preciso fazer nada, a menos que queira examinar o valor dex
, nesse caso, posso verificar se tenho um erro ou um valor "válido"; além disso, é um erro de tipo estático tentar usar um no lugar do outro, por isso não posso fazê-lo. Se os tipos de Elm funcionam como outras linguagens funcionais, eu posso realmente fazer coisas como compor valores de diferentes funções antes mesmo de saber se são erros ou não! Isso é típico das linguagens FP.Para entender essa afirmação, primeiro precisamos entender o que um sistema de tipo estático nos compra. Em essência, o que um sistema de tipo estático nos oferece é uma garantia: se o tipo de programa verificar, uma certa classe de comportamentos em tempo de execução não poderá ocorrer.
Isso soa ameaçador. Bem, um verificador de tipos é semelhante a um verificador de teoremas. (Na verdade, pelo isomorfismo de Curry-Howard, eles são a mesma coisa.) Uma coisa que é muito peculiar nos teoremas é que, quando você prova um teorema, prova exatamente o que o teorema diz, nada mais. (É por exemplo, por que, quando alguém diz "Eu provei este programa correto", você deve sempre perguntar "por favor defina 'correto'".) O mesmo se aplica aos sistemas de tipos. Quando dizemos "um programa é seguro para o tipo", o que queremos dizer não é que nenhum erro possível possa ocorrer. Só podemos dizer que os erros que o sistema de tipos promete impedir não podem ocorrer.
Portanto, os programas podem ter infinitos comportamentos de tempo de execução diferentes. Desses, infinitamente muitos são úteis, mas também infinitos são "incorretos" (para várias definições de "correção"). Um sistema de tipo estático nos permite provar que um determinado conjunto finito e fixo desses infinitos comportamentos incorretos de tempo de execução não pode ocorrer.
A diferença entre sistemas de tipos diferentes é basicamente em qual, quantos e quão complexos comportamentos de tempo de execução eles podem provar não ocorrer. Sistemas de tipo fraco, como o de Java, só podem provar coisas muito básicas. Por exemplo, Java pode provar que um método digitado como retornando a
String
não pode retornar aList
. Mas, por exemplo, pode não provar que o método não não vai voltar. Também não pode provar que o método não gera uma exceção. E não pode provar que não retornará o erro erradoString
- qualquerString
um satisfará o verificador de tipos. (E, claro, mesmonull
vai satisfazê-lo também.) Há ainda coisas muito simples que Java não pode provar, que é por isso que temos exceções, comoArrayStoreException
,ClassCastException
ou todo mundo favorito, oNullPointerException
.Sistemas de tipos mais poderosos como o da Agda também podem provar coisas como "retornará a soma dos dois argumentos" ou "retorna a versão classificada da lista passada como argumento".
Agora, o que os designers de Elm querem dizer com a afirmação de que eles não têm exceções de tempo de execução é que o sistema de tipos de Elm pode provar a ausência de (uma porção significativa de) comportamentos de tempo de execução que em outros idiomas não podem ser comprovados como não ocorrendo e, portanto, podem levar comportamento errado no tempo de execução (que, na melhor das hipóteses, significa uma exceção, na pior das hipóteses significa uma falha e, na pior das hipóteses, significa nenhuma falha, nenhuma exceção e apenas um resultado silenciosamente errado).
Portanto, eles não estão dizendo "não implementamos exceções". Eles estão dizendo "coisas que seriam exceções de tempo de execução em linguagens típicas com as quais os programadores comuns que vêm para Elm teriam experiência são capturados pelo sistema de tipos". Obviamente, alguém vindo de Idris, Agda, Guru, Epigram, Isabelle / HOL, Coq ou idiomas semelhantes verá Elm como muito fraco em comparação. A declaração é mais voltada para programadores Java, C♯, C ++, Objective-C, PHP, ECMAScript, Python, Ruby, Perl, ... típicos.
fonte
Elm pode garantir nenhuma exceção de tempo de execução pelo mesmo motivo C pode garantir nenhuma exceção de tempo de execução: O idioma não suporta o conceito de exceções.
Elm tem uma maneira de sinalizar condições de erro em tempo de execução, mas esse sistema não é exceções, é "Resultados". Uma função que pode falhar retorna um "Resultado" que contém um valor regular ou um erro. Elms é fortemente digitado, portanto, isso é explícito no sistema de tipos. Se uma função retornar sempre um inteiro, ele tem o tipo
Int
. Mas se ele retornar um número inteiro ou falhar, o tipo de retorno seráResult Error Int
. (A cadeia é a mensagem de erro.) Isso força você a lidar explicitamente com os dois casos no site de chamada.Aqui está um exemplo da introdução (um pouco simplificado):
A função
toInt
pode falhar se a entrada não for analisável, portanto, seu tipo de retorno éResult String int
. Para obter o valor inteiro real, você precisa "descompactar" via correspondência de padrões, que por sua vez o força a lidar com os dois casos.Resultados e exceções fundamentalmente fazem a mesma coisa, a diferença importante são os "padrões". As exceções aparecerão e encerrarão o programa por padrão, e você precisará identificá-las explicitamente se quiser manipulá-las. O resultado é o contrário - você é forçado a manipulá-los por padrão, portanto, você deve explicitamente passá-los até o topo, se quiser que eles encerrem o programa. É fácil ver como esse comportamento pode levar a um código mais robusto.
fonte
doSomeStuff(x: Int): Int
. Normalmente você espera que retorne umInt
, mas também pode gerar uma exceção? Sem olhar para o código fonte, você não pode saber. Por outro lado, uma linguagem B que codifica erros por meio de tipos pode ter a mesma função declarada assim:doSomeStuff(x: Int): ErrorOrResultOfType<Int>
(em Elm, esse tipo é realmente chamadoResult
). Ao contrário do primeiro caso, agora é imediatamente óbvio se a função pode falhar e você deve tratá-la explicitamente.this is how you program in languages such as ML or Haskell
Em Haskell, sim; ML, não. Robert Harper, um dos principais colaboradores do Standard ML e pesquisador da linguagem de programação, considera as exceções úteis . Os tipos de erro podem atrapalhar a composição da função nos casos em que você pode garantir que um erro não ocorra. Exceções também têm desempenho diferente. Você não paga para exceções que não são lançadas, mas você paga para verificar valores de erro de cada vez, e as exceções são uma maneira natural de expressar retrocesso em alguns algoritmosPrimeiro, observe que o seu exemplo de exceções de "deglutição" é geralmente uma prática terrível e completamente sem relação com a ausência de exceções em tempo de execução; quando você pensa sobre isso, você cometeu um erro em tempo de execução, mas optou por ocultá-lo e não fazer nada a respeito. Isso geralmente resulta em bugs difíceis de entender.
Essa pergunta pode ser interpretada de várias maneiras, mas como você mencionou Elm nos comentários, o contexto é mais claro.
Elm é, entre outras coisas, uma linguagem de programação estaticamente tipada . Um dos benefícios desse tipo de sistema de tipos é que muitas classes de erros (embora nem todas) são capturadas pelo compilador, antes que o programa seja realmente usado. Alguns tipos de erros podem ser codificados em tipos (como Elm
Result
eTask
), em vez de serem lançados como exceções. É isso que os projetistas do Elm querem dizer: muitos erros serão capturados no tempo de compilação em vez de no "tempo de execução", e o compilador forçará você a lidar com eles em vez de ignorá-los e esperar o melhor. Está claro por que essa é uma vantagem: é melhor que o programador perceba um problema antes que o usuário o faça.Observe que quando você não usa exceções, os erros são codificados de outras maneiras menos surpreendentes. Da documentação de Elm :
Os designers de olmos são um pouco ousados ao reivindicar "sem exceções de tempo de execução" , embora o qualifiquem como "na prática". O que eles provavelmente querem dizer é "menos erros inesperados do que se você estivesse codificando em javascript".
fonte
Result
eTask
parecem muito semelhantes aos mais familiaresEither
eFuture
de outros idiomas. Diferentemente das exceções, os valores desses tipos podem ser combinados e, em algum momento, você deve explicitamente lidar com eles: eles representam um valor válido ou um erro? Eu não leio mentes, mas esta falta de surpreender o programador é provavelmente o que Elm Designers entende por "sem exceções de tempo de execução" :)Elm afirma:
Mas você pergunta sobre exceções de tempo de execução . Há uma diferença.
No Elm, nada retorna um resultado inesperado. NÃO é possível escrever um programa válido no Elm que produz erros de tempo de execução. Portanto, você não precisa de exceções.
Portanto, a pergunta deve ser:
Se você puder escrever um código que nunca tenha erros de tempo de execução, seus programas nunca travarão.
fonte