2010-05-22 2 views
1

Possible en double:
Is it possible for a Perl subroutine to force its caller to return?Comment un Perl peut-il forcer son appelant à revenir?

Je veux écrire un sous-programme qui provoque l'appelant à revenir sous certaines conditions. Ceci est destiné à être utilisé comme un raccourci pour valider l'entrée d'une fonction. Ce que j'ai à ce jour est:

sub needs($$) { 
    my ($condition, $message) = @_; 

    if (not $condition) { 
     print "$message\n"; 
     # would like to return from the *parent* here 
    } 

    return $condition; 
} 

sub run_find { 
    my $arg = shift @_; 
    needs $arg, "arg required" or return; 
    needs exists $lang{$arg}, "No such language: $arg" or return; 

    # etc. 
} 

L'avantage de retour de l'appelant dans needs serait alors éviter d'avoir à écrire la or return répétitive à l'intérieur run_find et des fonctions similaires.

+0

Vous réalisez que, comme vous l'avez écrit, 'needs' sera toujours' return $ condition', n'est-ce pas? – Zaid

Répondre

4

Je pense que vous concentrer sur la mauvaise chose ici. Je fais ce genre de chose avec Data :: Constraint, Brick, etc. et j'en parle dans Mastering Perl. Avec un peu d'intelligence et de réflexion sur la structure de votre programme et les fonctionnalités dynamiques de Perl, vous n'avez pas besoin d'une telle approche réglementaire et procédurale. Cependant, la première chose que vous devez comprendre est ce que vous voulez vraiment savoir dans ce sous-programme d'appel. Si vous voulez juste savoir oui ou non, c'est plutôt facile. Le problème avec votre needs est que vous envisagez de l'appeler une fois pour toutes les conditions, ce qui vous oblige à utiliser needs pour contrôler le flux du programme. C'est la mauvaise façon de faire. needs est seulement là pour vous donner une réponse. Son travail n'est pas de changer l'état du programme. Cela devient beaucoup moins utile si vous l'utilisez à mauvais escient car un autre sous-programme appelant peut vouloir continuer même si needs renvoie la valeur false. Appelez-le une fois et laissez-le revenir une fois. Le sous-programme appelant utilise la valeur de retour pour décider de ce qu'il doit faire.

La structure de base implique une table que vous transmettez à needs. Ceci est votre profil de validation.

 
sub run_find { 
    my $arg = shift @_; 
    return unless needs [ 
     [ sub { $arg }, "arg required" ], 
     [ sub { exists $lang{$arg} }, "No such language: $arg" ], 
     ]; 
    } 
    ... 
    } 

Vous construisez votre table selon vos besoins. Dans needs vous traitez juste la table:

sub needs($$) { 
    my ($table) = @_; 

    foreach $test (@$table) { 
     my($sub, $message) = @$test; 
     unless($sub->(...)) { 
      print $message; 
      return 
      } 
     } 

    return 1; 
    } 

Maintenant, la chose vraiment cool avec cette approche est que vous ne devez pas savoir à l'avance la table. Vous pouvez tirer cela de la configuration ou d'une autre méthode. Cela signifie également que vous pouvez changer la table dynamiquement. Maintenant, votre code se rétrécit un peu:

 
sub run_find { 
    my $arg = shift @_; 
    return unless needs($validators{run_find}); 
    ... 
    } 

Vous pouvez continuer avec ceci. Dans Maîtriser Perl Je montre quelques solutions qui suppriment complètement cela du code et le déplacent dans un fichier de configuration. Autrement dit, vous pouvez modifier les règles métier sans modifier le code. Rappelez-vous, presque à chaque fois que vous tapez la même séquence de caractères, vous vous trompez probablement.:)

+0

+ 1 Je dois dire que l'idée de la table est fantastique. – Zaid

+0

Oui, l'idée de table répond à mes besoins. Il ne nécessite pas de farfelus avec des portées, mais il réduit encore la verbosité. –

4

On dirait que vous réinventez la gestion des exceptions.

La fonction needs ne devrait pas déduire magiquement son parent et interrompre le flux de contrôle du parent - c'est de mauvaises manières. Que faire si vous ajoutez des fonctions supplémentaires à la chaîne d'appel, et vous devez revenir en arrière deux ou même trois fonctions? Comment pouvez-vous déterminer cela par programme? L'appelant s'attend-il à ce que sa fonction revienne tôt? Vous devez suivre le principe de moindre surprise si vous voulez éviter les bogues - et cela signifie utiliser des exceptions pour indiquer qu'il ya un problème, et ayant l'appelant décider comment traiter avec elle:

use Carp; 
use Try::Tiny; 

sub run_find { 
    my $arg = shift; 
    defined $arg or croak "arg required"; 
    exists $lang{$arg} or croak "no such language: $arg"; 

    ... 
} 

sub parent { 
    try { run_find('foo') } 
    catch { print [email protected]; } 
} 

Tout code intérieur le bloc try est spécial: si quelque chose meurt, l'exception est interceptée et enregistrée dans [email protected]. Dans ce cas, le bloc catch est exécuté, ce qui imprime l'erreur à STDOUT et le flux de contrôle continue normalement. Clause de non-responsabilité: la gestion des exceptions en Perl est une douleur. Je recommande Try::Tiny, qui protège contre beaucoup de gotchas communs (et fournit une sémantique d'essai/catch familière) et Exception::Class pour faire rapidement des objets d'exception afin que vous puissiez distinguer entre les erreurs de Perl et les vôtres.

Pour la validation des arguments, il peut être plus facile d'utiliser un module CPAN tel que Params::Validate.

+0

Je reçois seulement des messages d'erreur si j'écris "catch {print $ _;}" au lieu de "catch {print $ @;}" –

+0

Le problème est que l'appelant de 'run_find' est un module hors de mon contrôle (Terme: : Shell, si vous y tenez), et cet appelant n'emballe pas mon appel dans 'eval'. Comme je ne veux pas détruire tout le processus, je dois quitter normalement 'run_find' et gérer les erreurs d'une autre façon. –

0

Cela n'a aucun sens de faire les choses de cette façon; ironiquement, vous n'avez pas besoin de needs.

Voici pourquoi.

  • run_find est mal écrit. Si votre première condition est vraie, vous ne testerez jamais la seconde puisque vous aurez déjà return ed.
  • Les fonctions warn et die vous fourniront tout de même un comportement d'impression et/ou de sortie.

Voilà comment j'écrire votre run_find sous si vous voulez mettre fin à l'exécution si votre argument ne tient pas (renommé à well_defined):

sub well_defined { 

    my $arg = shift; 
    $arg or die "arg required"; 
    exists $lang{$arg} or die "no such language: $arg"; 
    return 1; 
} 

Il devrait y avoir un moyen de return 0 et warn au en même temps, mais je vais devoir jouer avec un peu plus.

run_find peut aussi écrire à return 0 et le message warn approprié si les conditions ne sont pas remplies, et return 1 si elles sont (rebaptisés à well_defined).

sub well_defined { 

    my $arg = shift; 
    $arg or warn "arg required" and return 0; 
    exists $lang{$arg} or warn "no such language: $arg" and return 0; 
    return 1; 
} 

Cela permet un comportement booléenne-esque, comme illustré ci-dessous:

perform_calculation $arg if well_defined $arg; # executes only if well-defined 
+0

Que diriez-vous "avertir" pas un tel langage ", retour 0;'? – Ether

+0

@Ether: Essayé votre suggestion dans un doubleur Perl, il ne «warn» pas correctement: 'perl -e" sub rf {$ arg = décalage; $ arg ou warn qq (No arg), retourne 0; 1;} print qq (Oui) si rf; "'. Remplacer le ',' par 'and' fonctionne bien. – Zaid

+0

ah oui, j'ai oublié que 'warn' prend une liste, donc il va engloutir tout, sauf si on ajoute des parenthèses pour montrer où ses arguments finissent:' warn ("no such language"), return 0; ' – Ether

Questions connexes