boucle de Perl foreach avec des règles de fermeture de fonction

le code suivant

#!/usr/bin/env perl

use strict;
use warnings;

my @foo = (0,1,2,3,4);

foreach my $i (@foo) {
    sub printer {
        my $blah = shift @_;
        print "$blah-$in";
    }

    printer("test");
}

ne fait pas ce à quoi je m'attendais.

Ce qui se passe exactement? (Je m'attends à imprimer "test-0ntest-1ntest-2ntest-3ntest-4n")

8
demandé sur Snark 2011-07-05 22:58:51

2 réponses

le problème est que Le sub name {...} construire ne peut pas être imbriqué comme ça dans un for boucle.

La raison en est que sub name {...} signifie vraiment BEGIN {*name = sub {...}} et pour commencer les blocs sont exécutés dès qu'ils sont analysées. Ainsi, la compilation et la liaison variable du sous-programme se produisent au moment de la compilation, avant que la boucle for n'ait une chance de s'exécuter.

ce que vous voulez faire est de créer un sous-programme anonyme, qui liera ses variables à runtime:

#!/usr/bin/env perl

use strict;
use warnings;

my @foo = (0,1,2,3,4);

foreach my $i (@foo) {
    my $printer = sub {
        my $blah = shift @_;
        print "$blah-$i\n";
    };

    $printer->("test");
}

impression

test-0
test-1
test-2
test-3
test-4

vraisemblablement dans votre cas d'utilisation réelle, ces fermetures seront chargées dans un tableau ou un hachage de sorte qu'elles puissent être consultées plus tard.

vous pouvez toujours utiliser des identificateurs bareword avec des fermetures, mais vous devez faire un petit travail supplémentaire pour vous assurer que les noms sont visibles au moment de la compilation:

BEGIN {
    for my $color (qw(red blue green)) {
        no strict 'refs';
        *$color = sub {"<font color='$color'>@_</font>"}
    }
}

print "Throw the ", red 'ball';  # "Throw the <font color='red'>ball</font>"
19
répondu Eric Strom 2011-07-05 19:05:26

la réponse D'Eric Strom est correcte, et probablement ce que vous vouliez voir, mais n'entre pas dans les détails de la reliure.

une brève note sur lexical lifespan: les lexiques sont créés au moment de la compilation et sont en fait disponibles avant même que leur portée soit saisie, comme cet exemple le montre:

my $i;
BEGIN { $i = 42 }
print $i;

par la Suite, quand ils sortent de la portée, ils deviennent indisponibles jusqu'à la prochaine fois, ils sont dans la portée:

print i();
{
    my $i;
    BEGIN { $i = 42 }
    # in the scope of `my $i`, but doesn't actually
    # refer to $i, so not a closure over it:
    sub i { eval '$i' }
}
print i();

Dans votre code, la fermeture est liée à la première lexicale $i au moment de la compilation. Cependant, les boucles foreach sont un peu étranges; tandis que le my $i crée en fait un lexical, la boucle foreach ne l'utilise pas; au lieu de cela, elle l'aliène à l'une des valeurs en boucle à chaque itération et la restitue ensuite à son état d'origine après la boucle. Votre fermeture est donc la seule chose qui renvoie au lexique original $i.

Une légère variation montre plus de complexité:

foreach (@foo) {
    my $i = $_;
    sub printer {
        my $blah = shift @_;
        print "$blah-$i\n";
    }

    printer("test");
}

Ici, l'original $i est créé au moment de la compilation et de la fermeture se lie à la première itération de la boucle fixe, mais la deuxième itération de la boucle crée un nouveau $i sans lien avec la fermeture.

7
répondu ysth 2011-07-05 19:40:32