― Web Technology and Life ―

Perlの関数eachの罠

2011-04-10
グローバルな感じのハッシュをeachにかけていたら想定外の動きをしたので、それを備忘録的にメモです。

はまったコード

わかりやすくしたのでコードの意味はわけわからなくなっていますが、こんなコードがあるとします。

#!/usr/bin/env perl
use strict;
use warnings;

my $table_checked_fg_columns = +{
    hoge => +[qw/
        a_checked_fg b_checked_fg c_checked_fg
    /],
    moge => +[qw/
        a_checked_fg b_checked_fg c_checked_fg
    /],
    huga => +[qw/
        a_checked_fg b_checked_fg c_checked_fg
    /],
};

while(1){
    warn 'loop';
    last if(monitor());
}

sub monitor {
    my ($self) = @_;

    while (my ($table,$checked_fg_columns) = each %$table_checked_fg_columns) {
        for my $checked_fg_column (@$checked_fg_columns) {
            
            return 0 ;
        }
    }

    return 1;
}

一見、monitorというサブルーチンが0を返し続けて、whlie(1)の中でずっと「loop」を出力し続ける無限ループに見えますがそうではありません。出力結果は以下のようになります。

loop at ./test.pl line 18.
loop at ./test.pl line 18.
loop at ./test.pl line 18.
loop at ./test.pl line 18.

monitor関数が0を返した後、改めて呼ばれても、eachは$table_checked_fg_columnsのとりだしたところを覚えていて2回目は2行目、3回目は3行目感じで取り出していき、4回目の呼び出しでは、table_checked_fg_columnsが空になっているので、for文を通らないのです。

eachではなくkeysならうまくいく例

以下のように修正すると期待通りの無限ループになります

#!/usr/bin/env perl
use strict;
use warnings;

my $table_checked_fg_columns = +{
    hoge => +[qw/
        a_checked_fg b_checked_fg c_checked_fg
    /],
    moge => +[qw/
        a_checked_fg b_checked_fg c_checked_fg
    /],
    huga => +[qw/
        a_checked_fg b_checked_fg c_checked_fg
    /],
};

while(1){
    warn 'loop';
    last if(monitor());
}

sub monitor {
    my ($self) = @_;

    for my $table (keys %$table_checked_fg_columns) {
        for my $checked_fg_column (@{$table_checked_fg_columns->{$table}}) {
            
            return 0 ;
        }
    }

    return 1;
}

グローバル変数はやっぱりダメ?

$table_checked_fg_columnsは増えるかもしれないから、設定的なところにわけておきたかったので、実際には、クラス変数としてblessしていたのですが、やっぱりグローバル変数的なものはダメといういい例なのでしょうか。どういう持ち方がいいのやら、、、悩ましい。。。

Perl update_at : 2011-04-10T14:35:55
hirobanex.netの更新情報の取得
 RSSリーダーで購読する   
blog comments powered by Disqus