Distinguir entre exceção e falha em um bloco CATCH [RAKU]

9

Sabemos que uma falha pode ser tratada por um bloco CATCH.

No exemplo a seguir, criamos uma falha 'AdHoc' (em outro sub) e manipulamos a exceção em um bloco CATCH (em meu sub)

sub my-sub {
    try {
        CATCH {
            when X::AdHoc { say 'AdHoc Exception handled here'; .resume }
            default {say 'Other Exception'; .resume}
        }

        my $b = other-sub();

        $b.so ?? $b.say !! 'This was a Failure'.say;
    }
}

sub other-sub { fail 'Failure_X' }

my-sub();

A saída é a seguinte:

AdHoc Exception handled here
This was a Failure

Minha pergunta é: como podemos distinguir entre falha e uma exceção "normal" no bloco CATCH para diferenciar os dois casos?

jakar
fonte

Respostas:

12

A relação entre Failuree Exceptioné que a Failurepossui um Exception- ou seja, mantém o objeto de exceção como parte de seu estado. Algo assim:

class Failure {
    has Exception $.exception;
    # ...
}

Quando um Failure"explode", ele faz isso jogando o Exceptionque está dentro dele. Assim, o que atinge o CATCHbloco é o Exceptionobjeto, e não há link para o anexo Failure. (De fato, um determinado Exceptionobjeto poderia, em princípio, ser mantido por muitos Failures.)

Portanto, não há maneira direta de detectar isso. Do ponto de vista do design, você provavelmente não deveria estar e deve encontrar uma maneira diferente de resolver seu problema. A Failureé apenas uma maneira de adiar o lançamento de uma exceção e permitir que ela seja tratada como um valor; não se pretende que a natureza do problema subjacente mude porque é transmitida como um valor e não como uma transferência imediata do fluxo de controle. Infelizmente, o objetivo original não foi declarado na pergunta; você pode achar útil examinar exceções de controle, mas, caso contrário, talvez poste outra pergunta sobre o problema subjacente que você está tentando resolver. Provavelmente existe uma maneira melhor.

Para completar, eu vou note que não são formas indiretas que se pode detectar que o Exceptionfoi jogado por um Failure. Por exemplo, se você obtém o .backtraceobjeto de exceção e observa o pacote do quadro superior, é possível determinar que ele vem do Failure:

sub foo() { fail X::AdHoc.new(message => "foo") }
try {
    foo();
    CATCH {
        note do { no fatal; .backtrace[0].code.package ~~ Failure };
        .resume
    }
}

No entanto, isso depende muito dos detalhes da implementação que podem mudar facilmente, então eu não confiaria nisso.

Jonathan Worthington
fonte
Apenas para esclarecer as coisas, minha intenção é lidar apenas com exceções (no bloco CATCH). No caso de uma falha, quero retomar como se nada tivesse acontecido e deixar o resto do código (fora do CATCH) lidar com a falha. No meu exemplo, não esperava que a falha retornada disparasse a exceção contida! Tudo o que fiz foi obter o resultado em $ be verificá-lo como um Bool. Isso, a meu ver, não constitui "uso" da falha e, portanto, acionamento do bloco CATCH! Em vez disso, parece que o CATCH está sempre lidando com a exceção contida na falha !!
jakar 30/01
Além disso, no seu exemplo, sobre a maneira indireta de detectar uma falha, o Bool retornado (da verificação inteligente do tipo de falha) tem o valor "False". Mas eu esperava que fosse "Verdadeiro"! Perdi alguma coisa???
jakar 30/01
11
@jakar Um trybloco implica o use fatalpragma, o que significa que qualquer Failureretorno de uma chamada feita no bloco é imediatamente convertido em uma exceção. Apenas não use try; um CATCHpode ir em qualquer quarteirão de Raku (então, basta colocá-lo no nível do sub). Como alternativa, escreva no fatalna parte superior do seu trybloco.
Jonathan Worthington
E o meu segundo comentário?
jakar 30/01
11
Executando o exemplo, dei impressões Truena versão Rakudo que tenho localmente. Se isso não acontecer com o seu, isso prova o ponto de vista da fragilidade de fazer isso.
Jonathan Worthington
6

Basta remover o tryinvólucro:

sub my-sub {

#    try {              <--- remove this line...

        CATCH {
            when X::AdHoc { say 'AdHoc Exception handled here'; .resume }
            default {say 'Other Exception'; .resume}
        }

        my $b = other-sub();

        $b.so ?? $b.say !! 'This was a Failure'.say;

#    }                  <--- ...and this one

}

sub other-sub { fail 'Failure_X' }

my-sub();

Você usou try. A tryfaz algumas coisas, mas a coisa pertinente aqui é que ele diz ao Raku para promover imediatamente qualquer Failures no seu escopo, com exceções - que é o que você diz que não quer. Portanto, a solução mais simples é parar de fazer isso.


Esta resposta apenas repete verbalmente parte da explicação de jnthn (veja em particular comentários que ele escreveu abaixo de sua resposta). Mas eu não estava convencido de que todos os leitores perceberiam esse aspecto e não achei que um comentário ou dois sobre a resposta de jnthn ajudariam, daí essa resposta.

Escrevi isso como uma resposta da comunidade para garantir que não vou me beneficiar de nenhum voto positivo, porque obviamente não garante isso. Se o número de votos for suficiente, vamos excluí-lo.

raiph
fonte