perlsubの解釈について

perlsubクイズ - $ cat /var/log/shin の補足です。

perldocのperlsubには以下の様な記述があります。

ルーチンに渡されるすべての引数は配列 @_ に置かれます。 したがって、ある関数を二つの引数を付けて呼び出したならば、 その引数は $_[0] と $_[1] に格納されます。 配列 @_ は local 配列ですが、その要素は実際の スカラパラメータの別名です。 たとえば $_[0] が更新された場合、対応する引数が更新されます (更新できない場合にはエラーとなります)。 引数が、配列やハッシュの(関数が呼び出された時点では存在してない) 要素であった場合、その要素は(対応する別名が)修正されたり リファレンスが取られたときにのみ作成されます。 (以前の一部のバージョンの Perl では、この要素は代入が行われようが行われまいが 作成されていました。) 配列 @_ 全体に対する代入は別名を破棄し、何の引数も更新しません。

perlsub - Perl のサブルーチン - perldoc.jp

お世辞にもあまり優れた解説とは言えないので、私なりの解釈を述べます。

前半の解釈

まず、前半についての解釈です。

ルーチンに渡されるすべての引数は配列 @_ に置かれます。 したがって、ある関数を二つの引数を付けて呼び出したならば、 その引数は $_[0] と $_[1] に格納されます。 配列 @_ は local 配列ですが、その要素は実際の スカラパラメータの別名です。 たとえば $_[0] が更新された場合、対応する引数が更新されます (更新できない場合にはエラーとなります)。

perlsub - Perl のサブルーチン - perldoc.jp

Perlの基本のきですが、関数に引数を渡すと@_変数に格納されて、処理が関数内に移ります。

sub print_args {
    print join(' ', @_), "\n";
}

print_args(1, 2, 3, 4, 5); # 1 2 3 4 5

この時、@_の各要素は、関数に渡した引数のエイリアスになっています。

sub inc { $_[0]++ }
my $a = 0;
inc $a;
inc $a;
inc $a;
print $a, "\n"; # 3

引数に配列を渡した場合は、展開されますが、各要素がエイリアスである点は同様です。

sub inc_a { $_[$_]++ foreach (0..$#_) }
my @a = (1..10);
inc_a @a;
inc_a @a;
inc_a @a;
print join(' ', @a), "\n"; # 4 5 6 ... 13

つまりinc_a @ainc_a $a[0], $a[1], ..., $a[9]と同じであると考えればOKです。

ハッシュの場合は、キーの書き換えは出来ません。バリューの方はエイリアスになり、書き換えできます。

sub inc_h {
    $_[$_]++ foreach (0..$#_);
    print 'inc_h: ', join(' ', @_), "\n";
}
my %a = (1..10);
inc_h %a;
print join(' ', %a), "\n";
# inc_h: 2 3 4 5 8 9 10 11 6 7
# 1 3 3 5 7 9 9 11 5 7

関数内の@_における値は書き換わってますが、関数呼び出し元のハッシュのキー値は書き換わってないことが分かります。

配列やハッシュを複数渡した場合も同様です。

sub test {
    $_[4] = 100;
    $_[14] = 100;
}

my @a = (1..10);
my @b = (1..10);

test @a, @b;

print join(' ', @a), "\n"; # 1 2 3 4 100 6 7 8 9 10
print join(' ', @b), "\n"; # 1 2 3 4 100 6 7 8 9 10

「更新できない場合のエラー」というのはリテラルのハッシュを書き換えようとした時などに発生します。

sub inc { $_[0]++ }
my $a = \0;
inc $$a;
# Modification of a read-only value attempted

後半の解釈

次に、後半についての解釈を述べます。

引数が、配列やハッシュの(関数が呼び出された時点では存在してない) 要素であった場合、その要素は(対応する別名が)修正されたり リファレンスが取られたときにのみ作成されます。 (以前の一部のバージョンの Perl では、この要素は代入が行われようが行われまいが 作成されていました。) 配列 @_ 全体に対する代入は別名を破棄し、何の引数も更新しません。

perlsub - Perl のサブルーチン - perldoc.jp

これは、要するに以下のケースのことを表していると推測できます。

use strict;
use warnings;
use Data::Dumper;

sub test0 { my $t = $_[0] }
sub test1 { $_[0] = 100 }
sub test2 { \$_[0] }

my @a = (1, 2);
test0 $a[2];
print Dumper(\@a);
test1 $a[4];
print Dumper(\@a);
test2 $a[6];
print Dumper(\@a);

結果は以下のようになります。

$VAR1 = [
          1,
          2
        ];
$VAR1 = [
          1,
          2,
          undef,
          undef,
          100
        ];
$VAR1 = [
          1,
          2,
          undef,
          undef,
          100,
          undef,
          undef
        ];

test0のように参照するだけであれば@aに要素は作られませんが、test1のように値をセットしたり、test2のようにリファレンスがとられると、@aに要素が作られるということです。