• 差分
  • リロード
  • 一覧
  • 最終更新のRSS

Perl/Tips

サブルーチンの入れ子

Perlは入れ子のサブルーチンの扱いが若干トリッキーっぽい。

use strict;
use warnings;
sub outer {
    my ($a) = @_;
    sub inner {
        my ($b) = @_;
        print "$a and $b\n";
    }
    inner("bar");
}
sub another {
    my ($a) = @_;
    inner("another bar ($a)");
}

another("gir");  ## -> ' and another bar (gir)'  ※inner内の$aはundef
outer("A");      ## -> 'A and bar'
another("hoge"); ## -> 'A and another bar (hoge)'
outer("B");      ## -> 'A and bar'
another("foo");  ## -> 'A and another bar (foo)'

例えば上の例ではinnerはouterの中で定義されているが、anotherの中から呼べてしまう。(perl v5.14.2で確認)。 さらにinnerはouterから$aをクロージャ感覚で取り込んでいるが、そうはならず、

Variable "$a" will not stay shared at testnest.pl line 8.

という警告が出る。 上の例の結果からすると、outerとinnerで独立した$aというレキシカル変数が定義されているような動作に見える。

Perlはsubキーワードによる有名関数定義をコンパイル時に見つけると、それらを全てパッケージ名前空間に突っ込むようだ。

外部からアクセスできない入れ子のサブルーチンを定義したいなら、localでパッケージ名前空間をいじるのがベストらしい。

use strict;
use warnings;
sub outer {
    my ($a) = @_;
    local *inner = sub {
        my ($b) = @_;
        print "$a and $b\n";
    };
    inner("bar");
}
sub another {
    my ($a) = @_;
    inner("another bar ($a)");
}

## another("gir"); ## -> Error: Undefined subroutine &main::inner 
outer("A");        ## -> 'A and bar'

localの効果により、outerから抜けた時点でinnerの定義は消滅する。

一方、下のように、inner関数の実体をレキシカル変数に格納するのも一つの手。

sub outer {
    my ($a) = @_;
    my $inner; $inner = sub {
        my ($b) = @_;
        print "$a and $b\n";
    };
    $inner->("bar");
}

ただし、この方法ではinner関数を再帰呼び出しした時にメモリリークが起きる危険性がある。

use strict;
use warnings;
use VarGuard;
sub outer {
    my ($a) = @_;
    var_guard { print "inner freed for $a\n" } my $inner;
    $inner = sub {
        my ($b) = @_;
        if($b eq "") {
            return;
        }
        print "$a and $b\n";
        $inner->("");
    };
    $inner->("bar");
}

outer("A");
outer("B");
outer("C");
outer("D");

## --- Output:
## A and bar
## B and bar
## C and bar
## D and bar
## inner freed for B
## inner freed for C
## inner freed for D
## inner freed for A

VarGuardは変数が消滅した時にコールバックを実行するモジュール。 このように、outer関数が戻っても$innerは生き続け、プログラム終了時にまとめて消去される。

これは簡単に言うと、$innerとサブルーチン本体の間で循環参照が起きているせい。

なので、再帰呼び出しの終端条件で$innerによるサブルーチン参照を切ればメモリリークは起きない。

    $inner = sub {
        my ($b) = @_;
        if($b eq "") {
            undef $inner;    ## <- 追加
            return;
        }
        print "$a and $b\n";
        $inner->("");
    };

まあ、local *innerを使う方法の方が無難か。

参考

Last-modified: 2012-09-21 (金) 12:01:32 (4229d)