Distinguer l'exception et l'échec dans un bloc CATCH [RAKU]

9

Nous savons qu'un échec peut être géré par un bloc CATCH.

Dans l'exemple suivant, nous créons un échec «AdHoc» (dans other-sub) et nous gérons l'exception dans un bloc CATCH (dans my-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();

La sortie est la suivante:

AdHoc Exception handled here
This was a Failure

Ma question est cependant: comment pouvons-nous faire la distinction entre l'échec et une exception "normale" dans le bloc CATCH afin de différencier les deux cas?

jakar
la source

Réponses:

12

La relation entre Failureet Exceptionest que a Failurea un Exception- c'est-à-dire qu'il détient l'objet d'exception comme faisant partie de son état. Quelque chose comme ça:

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

Lorsqu'un Failure"explose", il le fait en lançant ce Exceptionqui est à l'intérieur. Ainsi, ce qui atteint le CATCHbloc est l' Exceptionobjet, et il n'y a pas de lien vers l'enveloppe Failure. (En fait, un Exceptionobjet donné pourrait en principe être détenu par de nombreux Failureart.)

Par conséquent, il n'y a aucun moyen direct de détecter cela. Du point de vue de la conception, vous ne devriez probablement pas l'être et devriez trouver une manière différente de résoudre votre problème. A Failureest juste un moyen de différer le lancement d'une exception et de la traiter comme une valeur; il n'est pas prévu que la nature du problème sous-jacent change car il est transmis comme une valeur plutôt que comme un transfert immédiat du flux de contrôle. Malheureusement, l'objectif initial n'était pas indiqué dans la question; vous trouverez peut-être utile d'examiner les exceptions de contrôle, mais sinon, posez peut-être une autre question sur le problème sous-jacent que vous essayez de résoudre. Il y a probablement une meilleure façon.

Par souci d'exhaustivité, je noterai qu'il existe des moyens indirects que l'on peut détecter que le a Exceptionété lancé par un Failure. Par exemple, si vous obtenez l' .backtraceobjet d'exception et regardez le package du cadre supérieur, il est possible de déterminer qu'il provient de Failure:

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

Cependant, cela dépend fortement des détails d'implémentation qui pourraient facilement changer, donc je ne m'y fierais pas.

Jonathan Worthington
la source
Juste pour clarifier les choses, mon intention est de ne gérer que les exceptions (dans le bloc CATCH). En cas d'échec, je veux reprendre comme si de rien n'était et laisser le reste du code (en dehors de CATCH) gérer l'échec. Dans mon exemple, je ne m'attendais pas à ce que l'échec renvoyé déclenche l'exception contenue! Tout ce que j'ai fait, c'est obtenir le résultat en $ b et le vérifier en tant que Bool. Cela, à mon avis, ne constitue pas une «utilisation» de l'échec et donc le déclenchement du bloc CATCH! Au lieu de cela, il semble que le CATCH gère toujours l'exception contenue dans l'échec !!
Jakar
De plus, dans votre exemple, à propos de la manière indirecte de détecter un échec, le Bool retourné (à partir de la vérification intelligente du type d'échec) a la valeur "False". Mais je m'attendais à ce que ce soit "vrai"! Ai-je oublié quelque chose???
Jakar
1
@jakar Un trybloc implique le use fatalpragma, ce qui signifie que tout Failureretour d'un appel effectué dans le bloc est immédiatement converti en exception. N'utilisez simplement pas try; un CATCHpeut aller dans n'importe quel bloc de Raku (il suffit donc de l'avoir au niveau du sub). Vous pouvez également écrire no fatalen haut de votre trybloc.
Jonathan Worthington
Et mon deuxième commentaire?
jakar
1
Exécuter l'exemple que j'ai donné Truesur la version Rakudo que j'ai en local. Si ce n'est pas le cas pour vous, cela prouve simplement la fragilité de faire cela.
Jonathan Worthington
6

Retirez simplement l' tryemballage:

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();

Vous avez utilisé try. A tryfait quelques choses, mais ce qui est pertinent ici, c'est qu'il dit à Raku de promouvoir immédiatement tout Failures dans sa portée en exceptions - c'est ce que vous dites que vous ne voulez pas . La solution la plus simple consiste donc à ne plus faire cela.


Cette réponse ne fait que répéter verbalement une partie de l'explication de jnthn (voir en particulier les commentaires qu'il a écrits ci-dessous sa réponse). Mais je n'étais pas convaincu que tous les lecteurs remarqueraient / comprendraient cet aspect, et je ne pensais pas qu'un ou deux commentaires sur la réponse de jnthn aideraient, d'où cette réponse.

J'ai écrit cela comme une réponse de la communauté pour m'assurer que je ne bénéficierai d'aucun vote positif, car cela ne le justifie évidemment pas. S'il obtient suffisamment de votes négatifs, nous le supprimerons simplement.

raiph
la source