Erros
Pony não apresenta exceções, pois você pode estar familiarizado com eles a partir de linguagens como Python, Java, C++ et al. No entanto, ele fornece um mecanismo simples de função parcial para auxiliar no manuseio de erros. As funções parciais e a palavra-chave de erro usada para levantá-las parecem semelhantes a exceções em outras linguagens, mas têm algumas diferenças semânticas importantes. Vamos ver como você trabalha com o erro do Pony e então como ele difere das exceções às quais você pode estar acostumado.
Levantando e manipulando erros
Um erro é levantado com o erro de comando. A qualquer momento, o código pode decidir declarar a ocorrência de um erro. A execução do código pára nesse ponto, e a cadeia de chamadas é desenrolada até que o manipulador de erros mais próximo seja encontrado. Tudo isso é verificado no momento da compilação, de modo que os erros não podem causar o travamento de todo o programa.
Os manipuladores de erros são declarados usando a sintaxe try-else.
try
callA()
if not callB() then error end
callC()
else
callD()
end
No código acima, callA() será sempre executado e o mesmo acontecerá com callB(). Se o resultado de callB() for verdadeiro, então procederemos a callC() da maneira normal e callD() não será executado.
Entretanto, se callB() retornar callB() falso, então um erro será levantado. Neste ponto, a execução irá parar e o manipulador de erros mais próximo será encontrado e executado. Neste exemplo, isto é, nosso outro bloco e assim callD() será executado.
Em ambos os casos, a execução prosseguirá com qualquer código que venha após o término da tentativa.
Eu tenho que fornecer um manipulador de erros? Não. O bloco de tentativa irá lidar com quaisquer erros, independentemente. Se você não fornecer um manipulador de erros, então nenhuma ação de tratamento de erros será tomada - a execução simplesmente continuará após a expressão try.
Se você quiser fazer algo que possa levantar um erro, mas você não se importa se isso acontecer, você pode simplesmente colocar um bloco de tentativa sem outro.
try
// Faça alguma coisa que possa gerar erro
End
Há alguma coisa que o meu manipulador de erros tenha que fazer? Não. Se você fornecer um manipulador de erros, ele deve conter algum código, mas depende inteiramente de você o que ele faz.
Qual é o valor resultante de um bloqueio de tentativa? O resultado de um bloco de tentativa é o valor da última declaração no bloco de tentativa, ou o valor da última declaração na outra cláusula se um erro for levantado. Se um erro for levantado e não houver outra cláusula fornecida, o valor do resultado será Nenhum.
Funções parciais
O Pony não exige que todos os erros sejam tratados imediatamente como em nossos exemplos anteriores. Em vez disso, as funções podem levantar erros que são tratados por qualquer código que os chame. Estas são chamadas funções parciais (este é um termo matemático que significa uma função que não tem um resultado definido para todas as entradas possíveis, ou seja, argumentos). Funções parciais devem ser marcadas como tais no Pony com um ?, tanto na assinatura da função (após o tipo de retorno) como no local da chamada (após os parênteses de fechamento).
Por exemplo, uma versão um tanto ou quanto artificial da função factorial que aceita um número inteiro assinado será um erro se for dada uma entrada negativa. Ela é definida apenas parcialmente sobre seu tipo de entrada válida.
fun factorial(x: I32): I32 ? =>
if x < 0 then error end
if x == 0 then
1
else
x * factorial(x - 1)?
End
Em todos os lugares onde um erro pode ser gerado no Pony (um comando de erro, uma chamada para uma função parcial, ou certas construções de linguagem embutida) deve aparecer dentro de um bloco de tentativa ou uma função que esteja marcada como parcial. Isto é verificado em tempo de compilação, assegurando que um erro não possa escapar do manuseio e travar o programa.
Antes do Pony 0.16.0, os locais de chamada de funções parciais não precisavam ser marcados com um ?. Isto frequentemente levava a confusão sobre as possibilidades de fluxo de controle ao ler o código. Ter cada local de chamada de funções parciais claramente marcado torna muito fácil para o leitor compreender imediatamente em qualquer lugar que um bloco de código pode saltar para o manipulador de erros mais próximo, tornando os possíveis caminhos de fluxo de controle mais óbvios e explícitos.
Construtores parciais e comportamentos
Os construtores de classes também podem ser marcados como parciais. Se um construtor de classe levantar um erro, a construção é considerada como tendo falhado e o objeto em construção é descartado sem nunca ser devolvido ao chamador.
Quando um ator construtor é chamado de ator é criado e uma referência a ele é devolvida imediatamente. No entanto, o código do construtor é executado de forma assíncrona em algum momento posterior. Se um construtor de ator levantar um erro, já seria tarde demais para comunicar isto ao autor da chamada. Por este motivo, os construtores de atores podem não ser parciais.
Os comportamentos também são executados de forma assíncrona e, portanto, não podem ser parciais pelo mesmo motivo.
Tentativa - blocos
Além de um outro manipulador de erros, um comando de tentativa pode ter um então bloqueio. Isto é executado após o resto da tentativa, quer um erro seja ou não levantado ou tratado. Expandindo nosso exemplo de antes:
try
callA()
if not callB() then error end
callC()
else
callD()
then
callE()
end
A chamadaE() será sempre executada. Se callB() retorna verdadeiro então a seqüência executada é callA(), callB(), callC(), callE(). Se callB() retorna falso, então a seqüência executada é callA(), callB(), callD(), callE().
Preciso ter outro manipulador de erros para ter um então bloqueio? Não. Você pode ter um bloqueio try-then sem um outro, se quiser.
Será que meu bloqueio então será sempre executado, mesmo se eu voltar dentro da tentativa? Sim, sua expressão então será sempre executada quando o bloco de tentativa estiver completo. A única maneira de não ser é se a tentativa nunca se completar (devido a um loop infinito), a máquina é desligada, ou o processo é morto (e então, talvez).
Com blocos
A com expressão pode ser usado para garantir o descarte de um objeto quando ele não é mais necessário. Um caso comum é uma conexão de banco de dados que precisa ser fechada após o uso para evitar vazamentos de recursos no servidor. Por exemplo, um caso comum é uma conexão de banco de dados que precisa ser fechada após o uso para evitar vazamentos de recursos no servidor:
with obj = SomeObjectThatNeedsDisposing() do
// use obj
End
obj.dispose() será chamado se o código dentro do bloco com o bloco for concluído com sucesso ou levantar um erro. Para participar de um com expressão, o objeto que necessita de limpeza de recursos deve, portanto, fornecer um método de dispose():
class SomeObjectThatNeedsDisposing
// construtor, outras funções
fun dispose() =>
// liberar recursos
É possível fornecer uma outra cláusula, que é chamada apenas em casos de erro:
with obj = SomeObjectThatNeedsDisposing() do
// use obj
else
// só executa se um erro ocorreu
End
Vários objetos podem ser montados para descarte:
with obj = SomeObjectThatNeedsDisposing(), other = SomeOtherDisposableObject() do
// use obj
End
O valor de uma com expressão é o valor da última expressão no bloco, ou da última expressão no outro bloco, se houver uma e ocorrer um erro.
Construções de linguagem que podem gerar erros
A única linguagem construída que pode levantar um erro, que não seja o comando de erro ou chamar um método parcial, é o como comando. Isto converte o valor dado para o tipo especificado, se for possível. Se não for possível, então um erro é levantado. Isto significa que o comando como comando só pode ser usado dentro de um bloco de tentativa ou de um método parcial.
Comparação com exceções em outros idiomas
Os erros de pony comportam-se muito da mesma forma que os erros em C++, Java, C#, Python e Ruby. A principal diferença é que os erros do Pony não têm um tipo ou instância associada a eles. Isto os torna iguais às exceções em C++ se um literal fixo fosse sempre lançado, por exemplo, lançamento 3;. Esta diferença simplifica a manipulação de erros para o programador e permite um desempenho muito melhor na manipulação de erros em tempo de execução.
O outro manipulador em uma expressão de tentativa é como um catch(...) em C++, catch(Exceção e) em Java ou C#, exceto: em Python, ou rescue em Ruby. Como as exceções não têm tipos, não há necessidade de manipuladores para especificar tipos ou de manipuladores múltiplos em um único bloco de tentativa.
O então bloco em uma expressão try é exatamente como um finalmente em Java, C#, ou Python e garantir em Ruby.
Se necessário, os manipuladores de erro podem "reraise" usando o comando de erro dentro do manipulador.