サブルーチンの入れ子 †
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を使う方法の方が無難か。
参考