2010-01-26 3 views
29

Je suis consterné. OK, donc c'était probablement le plus amusantPerl bug que j'ai jamais trouvé. Même aujourd'hui, j'apprends de nouvelles choses sur Perl. Essentiellement, l'opérateur bascule .. qui retourne faux jusqu'au retour de la main côté gauche vrai, puis vrai jusqu'à ce que la droite côté retourne faux garder état global (ou qui est ce que je supposons.)Est-ce que l'opérateur de flip-flop de Perl est buggé? Il a un état global, comment puis-je le réinitialiser?

Puis-je le réinitialiser (peut-être ce serait un bon ajout à Perl   4-esque presque jamais utilisé reset())? Ou, n'y a-t-il aucun moyen d'utiliser cet opérateur en toute sécurité?

Je ne vois pas non plus cela (le bit de contexte global) documenté nulle part dans perldoc perlop est-ce une erreur?

code

use feature ':5.10'; 
use strict; 
use warnings; 

sub search { 
    my $arr = shift; 
    grep { !(/start/ .. /never_exist/) } @$arr; 
} 

my @foo = qw/foo bar start baz end quz quz/; 
my @bar = qw/foo bar start baz end quz quz/; 

say 'first shot - foo'; 
say for search \@foo; 

say 'second shot - bar'; 
say for search \@bar; 

Spoiler

$ perl test.pl 
first shot 
foo 
bar 
second shot 
+2

Bonne question! On dirait qu'il agit comme une fermeture. – Demosthenex

+0

Oh j'ai rencontré ceci - j'ai écrit une fonction en utilisant le flipflop, appelé deux fois et la deuxième fois il avait conservé l'état de la première. a cassé mon application! –

+2

Wow, je n'ai même jamais su que 'reset' existait. ++ juste pour ça. :) – friedo

Répondre

34

Il indique clairement:

Each ".." operator maintains its own boolean state. 

Il y a un certain flou là-bas sur ce que « chaque » signifie, mais je ne pense pas que la documentation serait bien servi par une explication complexe. Notez que les autres itérateurs de Perl (each ou contexte scalaire glob) peuvent entraîner les mêmes problèmes. Parce que l'état pour each est lié à un hachage particulier, pas un bit particulier de code, each peut être réinitialisé en appelant (même dans le contexte vide) keys sur le hachage. Mais pour glob ou .., il n'y a pas de mécanisme de réinitialisation disponible sauf en appelant l'itérateur jusqu'à ce qu'il soit réinitialisé. Un échantillon glob bug:

sub globme { 
    print "globbing $_[0]:\n"; 
    print "got: ".glob("{$_[0]}")."\n" for 1..2; 
} 
globme("a,b,c"); 
globme("d,e,f"); 
__END__ 
globbing a,b,c: 
got: a 
got: b 
globbing d,e,f: 
got: c 
Use of uninitialized value in concatenation (.) or string at - line 3. 
got: 

Pour l'ici sont trop curieux, quelques exemples où même .. dans la source est un autre opérateur ..:

fermetures séparées:

sub make_closure { 
    my $x; 
    return sub { 
     $x if 0; # Look, ma, I'm a closure 
     scalar($^O..!$^O); # handy values of true..false that don't trigger ..'s implicit comparison to $. 
    } 
} 
print make_closure()->(), make_closure()->(); 
__END__ 
11 

Commentez la ligne $x if 0 pour voir que les non-fermetures ont une seule opération .. partagée par toutes les "copies", la sortie étant 12.

Sujets:

use threads; 
sub coderef { sub { scalar($^O..!$^O) } } 
coderef()->(); 
print threads->create(coderef())->join(), threads->create(coderef())->join(); 
__END__ 
22 

code threadé commence par quel que soit l'état du .. avait été avant la création de fil, mais les changements à son état dans le fil sont isolés d'affecter quoi que ce soit d'autre.

Recursion:

sub flopme { 
    my $recurse = $_[0]; 
    flopme($recurse-1) if $recurse; 
    print " "x$recurse, scalar($^O..!$^O), "\n"; 
    flopme($recurse-1) if $recurse; 
} 
flopme(2) 
__END__ 
1 
1 
2 
    1 
3 
2 
4 

Chaque profondeur de récursivité est séparée .. opérateur.

+2

+1 - Comme l'idée d'utiliser des fermetures séparées. – mob

+1

ysth ++ Votre réponse est belle et géniale. Ceci est exactement ce que je cherchais. Merci. –

7

Le "opérateur de gamme" .. est décrite dans la rubrique "perlop Range opérateurs". En regardant à travers la doucmentation, il apparaît qu'il n'y a aucun moyen de réinitialiser l'état de l'opérateur ... Chaque instance de l'opérateur .. conserve son état propre, ce qui signifie qu'il n'y a aucun moyen de se référer à l'état d'un opérateur .. particulier.

On dirait qu'il est conçu pour les scripts très petits tels que:

if (101 .. 200) { print; } 

La documentation indique que c'est l'abréviation de

if ($. == 101 .. $. == 200) { print; } 

D'une certaine manière l'utilisation de $. est là implicite (points toolic sur dans un commentaire que cela est documenté aussi). L'idée semble être que cette boucle s'exécute une fois (jusqu'à $. == 200) dans une instance donnée de l'interpréteur Perl, et par conséquent vous n'avez pas besoin de vous soucier de réinitialiser l'état de la bascule ...

Cet opérateur ne semble pas trop utile dans un contexte réutilisable plus général, pour les raisons que vous avez identifiées.

+0

Une citation des docs: "Si l'un des opérandes de scalar .. est une expression constante, cet opérande est considéré comme vrai s'il est égal (==) au numéro de ligne d'entrée actuel (la variable $.)." 101 et 202 sont deux expressions constantes. – toolic

+0

à droite, je n'ai pas vu dans les documents 'où le contexte global de l'opérateur a été mentionné' - alors que j'ai mentionné 'perldoc perlop', je n'ai pas qualifié la doc-question assez désolé pour la confusion. –

2

J'ai trouvé ce problème, et pour autant que je sache, il n'y a aucun moyen de le réparer. Le résultat est - n'utilisez pas l'opérateur .. dans les fonctions, sauf si vous êtes sûr de le laisser dans l'état faux lorsque vous quittez la fonction, sinon la fonction peut renvoyer une sortie différente pour la même entrée (ou afficher un comportement différent pour le même entrée).

+0

Cela devrait probablement apparaître clairement dans la documentation. * pokes brian d foy * – Ether

+0

Je peux soumettre un correctif doc demain, mais il semblerait que ce soit mieux corrigé en corrigeant perl pour que le '..' ne soit pas aussi global, et éventuellement réinitialisé avec' reset() ' –

+1

I ajouté une note très courte perlop, mais je pensais que c'était clair avant. Le problème survient toujours lorsque vous lisez des choses qui ne sont pas là, comme si vous étiez à portée de la main quand rien ne dit que c'est le cas. –

1

Chaque utilisation de l'opérateur .. conserve son propre état. Comme l'a dit Alex Brown, vous devez le laisser dans l'état faux lorsque vous quittez la fonction.Peut-être que vous pourriez faire quelque chose comme:

sub search { 
    my $arr = shift; 
    grep { !(/start/ || $_ eq "my magic reset string" .. 
      /never_exist/ || $_ eq "my magic reset string") } 
     (@$arr, "my magic reset string"); 
} 
+0

Je n'utiliserais pas le mot 'use'. Je pense qu'il est plus juste de dire «apparition dans la source» –

+2

@Evan Carroll: voir ma réponse pour savoir si c'est inexact. – ysth

+0

Eh oui, vous êtes évidemment le meilleur poste perl'er. (J'aurais deviné les threads) –

7

Une solution de contournement/pirater/tricher pour votre cas particulier est d'ajouter la valeur finale à votre tableau:

sub search { 
    my $arr = shift; 
    grep { !(/start/ .. /never_exist/) } @$arr, 'never_exist'; 
} 

Cela garantira que l'ERS de l'opérateur de gamme sera finalement vrai.

Bien sûr, ce n'est en aucun cas une solution générale. À mon avis, ce comportement n'est pas clairement documenté. Si vous pouvez construire une explication claire, vous pouvez appliquer un correctif à perlop.pod via perlbug. Est-ce que quelqu'un peut clarifier ce qu'est le problème avec la documentation?

+0

Ceci est bien sûr une simple solution de contournement valide (une solution viable), et j'ai voté toutes les réponses à cette question. Mais, actuellement, je cherche à en savoir plus sur l'opérateur. –

+3

Ou ne le poussez pas sur le tableau en premier lieu: 'grep {! (/ Start/../never_exist /)} (@ $ arr, 'never_exist');' –

+0

@ Brian/Dave: Oui, je était négligent. J'ai mis à jour le code. – toolic

18

L'astuce n'est pas d'utiliser la même flip-flop donc vous n'avez pas d'état à s'inquiéter. Il suffit de faire une fonction de générateur pour vous donner un nouveau sous-programme avec une nouvelle bascule que vous utilisez une seule fois:

sub make_search { 
    my($left, $right) = @_; 
    sub { 
     grep { !(/\Q$left\E/ .. /\Q$right\E/) } @{$_[0]}; 
     } 
} 

my $search_sub1 = make_search('start', 'never_existed'); 
my $search_sub2 = make_search('start', 'never_existed'); 


my @foo = qw/foo bar start baz end quz quz/; 

my $count1 = $search_sub1->(\@foo); 
my $count2 = $search_sub2->(\@foo); 

print "count1 $count1 and count2 $count2\n"; 

J'écris aussi à ce sujet dans Make exclusive flip-flop operators.

Questions connexes