サブルーチンと引数と @_ とエイリアス
sela @ Okinawa.pm 2017/04/01
「Perl 入学式 第4回 サブルーチン/正規表現編」の例示より
https://github.com/perl-entrance-org/workshop-2016/blob/master/4th/slide.md#リファレンス渡し-2 
配列を 2 つ作って、それを引数としてサブルーチンに渡します。
# 2 つの配列
my @hoge = qw/hoge fuga/;
my @foo = qw/foo bar baz/;
# 2 つの配列を受け取って個別に出力する
sub output {
# 引数の 2 つの配列を 2 つの配列で受け取っているつもり…
my (@array1, @array2) = @_;
# それぞれの配列を出力
print '@array1 = '. join(',', @array1)."n";
print '@array2 = '. join(',', @array2)."n";
}
output(@hoge,@foo);
# 出力
@array1 = hoge,fuga,foo,bar,baz
@array2 =
2 つの配列引数が 1 つの配列にまとまっています。
# 2 つの配列
my @hoge = qw/hoge fuga/;
my @foo = qw/foo bar baz/;
# 2 つの配列を受け取って個別に出力する
sub output {
# 引数の 2 つの配列を 2 つの配列で受け取っているつもり…
my (@array1, @array2) = @_;
# それぞれの配列を出力
print '@array1 = '. join(',', @array1)."n";
print '@array2 = '. join(',', @array2)."n";
}
output(@hoge,@foo);
# 出力
@array1 = hoge,fuga,foo,bar,baz
@array2 =
なぜなら組み込みの配列変数「 @_ 」は、指定された引数が配列であろう
がハッシュであろうが、各要素をばらばらに展開して単一のスカラデータ
として保持するからです。
sub output {
my (@array1, @array2) = @_;
・・・
つまり、この時「 @_ 」は引数 @hoge, @foo の値を
“hoge”,”fuga”,”foo”,”bar”,”baz"
というリストデータで保持しているので、 サブルーチンの中で 2 つの
配列にセットしているつもりでも、実際には最初の配列「 @array1 」
にすべてセットされることになります。
sub output {
my (@array1, @array2) = @_;
# @_ の値は “hoge”,”fuga”,”foo”,”bar”,”baz"
# つまり
# $_[0] = hoge
# $_[1] = fuga
# $_[2] = foo
# $_[3] = bar
# $_[4] = bat
・・・
これを回避するためには、サブルーチンの引数を「 リファレンス 」と
して渡します。
# 2 つの配列
my @hoge = qw/hoge fuga/;
my @foo = qw/ foo bar baz/;
# 2 つの配列をリファレンスとして受け取って個別に出力する
sub output {
# 引数は 2 つのリファレンスなのでスカラで受ける
my ($array1, $array2) = @_;
# それぞれの配列リファレンスを配列全体 ( @$ ) としてデリファレンスして出力
print '@$array1 = '. join(',', @$array1)."n";
print '@$array2 = '. join(',', @$array2)."n";
}
# 引数の配列はリファレンスとして渡す
output(@hoge,@foo);
# 出力
@$array1 = hoge,fuga
@$array2 = foo,bar,baz
無事に、2 つの配列を 2 つの配列として出力できました。
# 2 つの配列
my @hoge = qw/hoge fuga/;
my @foo = qw/ foo bar baz/;
# 2 つの配列をリファレンスとして受け取って個別に出力する
sub output {
# 引数は 2 つのリファレンスなのでスカラで受ける
my ($array1, $array2) = @_;
# それぞれの配列リファレンスを配列全体 ( @$ ) としてデリファレンスして出力
print '@$array1 = '. join(',', @$array1)."n";
print '@$array2 = '. join(',', @$array2)."n";
}
# 引数の配列はリファレンスとして渡す
output(@hoge,@foo);
# 出力
@$array1 = hoge,fuga
@$array2 = foo,bar,baz
これは、リファレンスの値がデータを格納した場所のアドレスであること、
つまり、もともとがスカラデータであることを利用しています。
「Perl 入学式 第4回 サブルーチン/正規表現編」の解説を抜粋 
リファレンス渡し
• 配列と同様に, ハッシュもリファレンスで渡すことができます.
• 更に, リファレンスで渡す場合, 配列をそのまま渡した時のように, デー
タのコピーが発生しません.
・・・
更に, リファレンスで渡す場合, 配列をそのまま渡した時のように, データ
のコピーが発生しません.
sub output {
# ここで コピー が発生しないということ (引数がリファレンスなら)
my ($array1, $array2) = @_;
print '@$array1 = '. join(',', @$array1)."n";
print '@$array2 = '. join(',', @$array2)."n";
}
output(@hoge,@foo);
”リファレンスだからこそ” @_ へのコピーが発生しない印象ですが、
これは、サブルーチン内の処理で値のコピーが発生しないということです。
# @hoge,@foo ——-> @_ へのコピーが発生しない
output(@hoge,@foo);
そもそも @_ へは、”リファレンスでない” 引数を対象とした場合で
もデータのコピー (値渡し) を行うことはありません。
サブルーチンへの引数は、どのようなデータが引数として指定されても、@_
にセットされる際のデータコピー (値渡し) は発生しません。
perlsub - Perl のサブルーチン
http://perldoc.jp/docs/perl/5.22.1/perlsub.pod
Any arguments passed in show up in the array @_. (They may also show
up in lexical variables introduced by a signature; see "Signatures"
below.) Therefore, if you called a function with two arguments, those
would be stored in $_[0] and $_[1]. The array @_ is a local array,
but its elements are aliases for the actual scalar parameters. In
particular, if an element $_[0] is updated, the corresponding
argument is updated (or an error occurs if it is not updatable).
ルーチンに渡されるすべての引数は配列 @_ に置かれます。 (シグネチャに
よって導入されたレキシカル変数にも現れることがあります; 後述する
"Signatures" を参照してください。) したがって、ある関数を二つの引数を
付けて呼び出したならば、 その引数は $_[0] と $_[1] に格納されます。
配列 @_ は local 配列ですが、その要素は実際の スカラパラメータの別名
です。 たとえば $_[0] が更新された場合、対応する引数が更新されます
(更新できない場合にはエラーとなります)。
別名 = エイリアス (alias)
perlsub - Perl のサブルーチン
http://perldoc.jp/docs/perl/5.22.1/perlsub.pod
引数に名前を付けるためにプライベート変数のリストに代入する:
sub maybeset {
my($key, $value) = @_;
$Foo{$key} = $value unless $Foo{$key};
}
これは、参照渡しを値渡しにする効果もあります; なぜなら、値のコピーを
代入しているからです。 このようにしないのであれば、関数は自由に @_ の
値をその場で 書き換えることができ、それはそのまま呼び出し側の値を変更
します。
upcase_in($v1, $v2); # this changes $v1 and $v2
sub upcase_in {
for (@_) { tr/a-z/A-Z/ }
}
どういうことかというと、
# @hoge, @foo の要素のリファレンスとその値出力
print $_. " : ". "$_n" for @hoge,@foo;
print "-----n";
# 配列を値渡し
output3(@hoge,@foo);
sub output3 {
# @_ の要素のリファレンスとその値出力
print $_. " : ". "$_n" for @_;
}
こういうことです。
このコードでは、引数とする配列 @hoge, @foo の各要素のリファレン
スとその値、それからサブルーチンの中では引数を格納した @_ の各要
素のリファレンスとその値を出力します。
サブルーチンには 2 つの配列を値渡し (つまりリファレンスではない)
をしています。
SCALAR(0x7f8dde803268) : hoge
SCALAR(0x7f8dde803418) : fuga
SCALAR(0x7f8dde828928) : foo
SCALAR(0x7f8dde828940) : bar
SCALAR(0x7f8dde828970) : baz
-----
SCALAR(0x7f8dde803268) : hoge
SCALAR(0x7f8dde803418) : fuga
SCALAR(0x7f8dde828928) : foo
SCALAR(0x7f8dde828940) : bar
SCALAR(0x7f8dde828970) : baz
実行結果は次のとおり。
サブルーチンの引数では値渡し (通常は値がコピーされる ) をしてい
るにもかかわらず、サブルーチン内の @_ が格納しているデータは、も
との配列が持っている各要素 (スカラデータ) と同じアドレスをもって
います。
SCALAR(0x7f8dde803268) : hoge
SCALAR(0x7f8dde803418) : fuga
SCALAR(0x7f8dde828928) : foo
SCALAR(0x7f8dde828940) : bar
SCALAR(0x7f8dde828970) : baz
-----
SCALAR(0x7f8dde803268) : hoge
SCALAR(0x7f8dde803418) : fuga
SCALAR(0x7f8dde828928) : foo
SCALAR(0x7f8dde828940) : bar
SCALAR(0x7f8dde828970) : baz
つまり、
サブルーチンへの引数は、それがリファレンスでなくても (値渡しで
も)、内部的には参照渡しがされていることがわかります。
これを、@_ の各要素は引数のスカラデータの「エイリアスとして扱わ
れる」といいます。
このように、@_ への参照渡しは個々の要素としてのみ行われるので、
配列やハッシュなどのデータ構造は、サブルーチンの中では配列やハッ
シュとしての構造を保っていません。なぜなら、前述したとおりサブ
ルーチンの引数はばらばらに分解されて、スカラデータのリストとして
扱われるからです。
use Data::Dumper;
my %hash = (key1 => 'val1', key2 => 'val2');
my @arry = qw/elm1 elm2 elm3/;
output4(%hash, @arry);
sub output4 {
# @_ のデータ構造を出力
print Dumper @_;
}
ためしにハッシュと配列を引数として渡してみると、
$VAR1 = [
'key2',
'val2',
'key1',
'val1',
'elm1',
'elm2',
'elm3'
];
@_ のデータ構造は次のような単純なリストデータとして扱われます。
ですから結果として、サブルーチンの中で配列やハッシュなどのデータ
構造をデータ構造としてそのまま扱いたい場合は、リファレンスとして
引数に指定すると便利になります。
use Data::Dumper;
my %hash = (key1 => 'val1', key2 => 'val2');
my @arry = qw/elm1 elm2 elm3/;
# 引数はリファレンス
output4(%hash, @arry);
sub output4 {
# @_ のデータ構造を出力
print Dumper @_;
}
ハッシュと配列のリファレンスを引数として渡すと、
$VAR1 = [
{
'key2' => 'val2',
'key1' => 'val1'
},
[
'elm1',
'elm2',
'elm3'
]
];
@_ のデータ構造は、元のデータ構造を保持します。
まとめ
• サブルーチンへの引数は、内部的に参照渡しされる (エイリアスとし
て扱われる) ので、引数から @_ へのデータコピーは行われない。
• ただし、配列やハッシュなどのデータ構造は維持されないので、サブ
ルーチン内で引数のデータ構造をそのまま扱いたい場合はリファレン
ス/デリファレンスを活用すると便利 (というかスタンダード)。
• なお、エイリアスとして扱われるデータを変更すると、それはリファ
レンスと同じく参照渡しされたデータなので、引数となった元のデー
タを直接変更する結果になる。
• サブルーチンの他、foreach,map,grep などでも $_ が扱われるが、
それらも同様にエイリアスなので、元のデータに影響を与えたくない
場合は foreach my $tmp (LIST){…} などとして明示的なデータのコ
ピーを行う手法が一般的。
このトークで伝えたいこと
Perl ユーザは、ある程度のレベルから「リファレンス/デリファレン
ス」および無名配列リファレンス ( [ ] )、無名ハッシュリファレン
ス ( { } )、無名コードリファレンス ( sub{ } ) などを空気のよう
に扱うようになります。
また、サブルーチンの応用形である各種ライブラリ・モジュールの作
成・利用などでは、任意のデータ構造を適切に扱うために「リファレ
ンス/デリファレンス」の利用がほとんど必須になっています。
このため、彼ら (Perl 中級者以上) のいう「配列」や「ハッシュ」(
あるいはサブルーチン ) は、暗黙的にそれがリファレンスであること
が多いようです。
ですから、
• 初心者の立場から: それが素朴な配列・ハッシュなのかそれともリ
ファレンスなのかをしっかり確認しましょう。リファレンスの理解
があやふやなら他を差し置いてでもリファレンスの理解を確実にし
ておいたほうが後々の理解がスムーズになるはずです。
• 中級者以上の立場から: それが素朴な配列・ハッシュなのかそれと
もリファレンスなのかをしっかり説明しましょう。リファレンスの
理解があやふやなら他を差し置いてでもリファレンスの理解を確実
にしておいたほうがいいと思うよ (ニッコリ) と伝えてあげるとコ
ミュニケーションがスムーズになるはずです。
END.

20170401 alias

  • 1.
  • 2.
    「Perl 入学式 第4回サブルーチン/正規表現編」の例示より https://github.com/perl-entrance-org/workshop-2016/blob/master/4th/slide.md#リファレンス渡し-2 
  • 3.
  • 4.
    # 2 つの配列 my@hoge = qw/hoge fuga/; my @foo = qw/foo bar baz/; # 2 つの配列を受け取って個別に出力する sub output { # 引数の 2 つの配列を 2 つの配列で受け取っているつもり… my (@array1, @array2) = @_; # それぞれの配列を出力 print '@array1 = '. join(',', @array1)."n"; print '@array2 = '. join(',', @array2)."n"; } output(@hoge,@foo); # 出力 @array1 = hoge,fuga,foo,bar,baz @array2 = 2 つの配列引数が 1 つの配列にまとまっています。
  • 5.
    # 2 つの配列 my@hoge = qw/hoge fuga/; my @foo = qw/foo bar baz/; # 2 つの配列を受け取って個別に出力する sub output { # 引数の 2 つの配列を 2 つの配列で受け取っているつもり… my (@array1, @array2) = @_; # それぞれの配列を出力 print '@array1 = '. join(',', @array1)."n"; print '@array2 = '. join(',', @array2)."n"; } output(@hoge,@foo); # 出力 @array1 = hoge,fuga,foo,bar,baz @array2 = なぜなら組み込みの配列変数「 @_ 」は、指定された引数が配列であろう がハッシュであろうが、各要素をばらばらに展開して単一のスカラデータ として保持するからです。
  • 6.
    sub output { my(@array1, @array2) = @_; ・・・ つまり、この時「 @_ 」は引数 @hoge, @foo の値を “hoge”,”fuga”,”foo”,”bar”,”baz" というリストデータで保持しているので、 サブルーチンの中で 2 つの 配列にセットしているつもりでも、実際には最初の配列「 @array1 」 にすべてセットされることになります。
  • 7.
    sub output { my(@array1, @array2) = @_; # @_ の値は “hoge”,”fuga”,”foo”,”bar”,”baz" # つまり # $_[0] = hoge # $_[1] = fuga # $_[2] = foo # $_[3] = bar # $_[4] = bat ・・・ これを回避するためには、サブルーチンの引数を「 リファレンス 」と して渡します。
  • 8.
    # 2 つの配列 my@hoge = qw/hoge fuga/; my @foo = qw/ foo bar baz/; # 2 つの配列をリファレンスとして受け取って個別に出力する sub output { # 引数は 2 つのリファレンスなのでスカラで受ける my ($array1, $array2) = @_; # それぞれの配列リファレンスを配列全体 ( @$ ) としてデリファレンスして出力 print '@$array1 = '. join(',', @$array1)."n"; print '@$array2 = '. join(',', @$array2)."n"; } # 引数の配列はリファレンスとして渡す output(@hoge,@foo); # 出力 @$array1 = hoge,fuga @$array2 = foo,bar,baz 無事に、2 つの配列を 2 つの配列として出力できました。
  • 9.
    # 2 つの配列 my@hoge = qw/hoge fuga/; my @foo = qw/ foo bar baz/; # 2 つの配列をリファレンスとして受け取って個別に出力する sub output { # 引数は 2 つのリファレンスなのでスカラで受ける my ($array1, $array2) = @_; # それぞれの配列リファレンスを配列全体 ( @$ ) としてデリファレンスして出力 print '@$array1 = '. join(',', @$array1)."n"; print '@$array2 = '. join(',', @$array2)."n"; } # 引数の配列はリファレンスとして渡す output(@hoge,@foo); # 出力 @$array1 = hoge,fuga @$array2 = foo,bar,baz これは、リファレンスの値がデータを格納した場所のアドレスであること、 つまり、もともとがスカラデータであることを利用しています。
  • 10.
    「Perl 入学式 第4回サブルーチン/正規表現編」の解説を抜粋  リファレンス渡し • 配列と同様に, ハッシュもリファレンスで渡すことができます. • 更に, リファレンスで渡す場合, 配列をそのまま渡した時のように, デー タのコピーが発生しません. ・・・
  • 11.
  • 12.
    sub output { #ここで コピー が発生しないということ (引数がリファレンスなら) my ($array1, $array2) = @_; print '@$array1 = '. join(',', @$array1)."n"; print '@$array2 = '. join(',', @$array2)."n"; } output(@hoge,@foo); ”リファレンスだからこそ” @_ へのコピーが発生しない印象ですが、 これは、サブルーチン内の処理で値のコピーが発生しないということです。 # @hoge,@foo ——-> @_ へのコピーが発生しない output(@hoge,@foo); そもそも @_ へは、”リファレンスでない” 引数を対象とした場合で もデータのコピー (値渡し) を行うことはありません。
  • 13.
  • 14.
    perlsub - Perlのサブルーチン http://perldoc.jp/docs/perl/5.22.1/perlsub.pod Any arguments passed in show up in the array @_. (They may also show up in lexical variables introduced by a signature; see "Signatures" below.) Therefore, if you called a function with two arguments, those would be stored in $_[0] and $_[1]. The array @_ is a local array, but its elements are aliases for the actual scalar parameters. In particular, if an element $_[0] is updated, the corresponding argument is updated (or an error occurs if it is not updatable). ルーチンに渡されるすべての引数は配列 @_ に置かれます。 (シグネチャに よって導入されたレキシカル変数にも現れることがあります; 後述する "Signatures" を参照してください。) したがって、ある関数を二つの引数を 付けて呼び出したならば、 その引数は $_[0] と $_[1] に格納されます。 配列 @_ は local 配列ですが、その要素は実際の スカラパラメータの別名 です。 たとえば $_[0] が更新された場合、対応する引数が更新されます (更新できない場合にはエラーとなります)。
  • 15.
  • 16.
    perlsub - Perlのサブルーチン http://perldoc.jp/docs/perl/5.22.1/perlsub.pod 引数に名前を付けるためにプライベート変数のリストに代入する: sub maybeset { my($key, $value) = @_; $Foo{$key} = $value unless $Foo{$key}; } これは、参照渡しを値渡しにする効果もあります; なぜなら、値のコピーを 代入しているからです。 このようにしないのであれば、関数は自由に @_ の 値をその場で 書き換えることができ、それはそのまま呼び出し側の値を変更 します。 upcase_in($v1, $v2); # this changes $v1 and $v2 sub upcase_in { for (@_) { tr/a-z/A-Z/ } }
  • 17.
  • 18.
    # @hoge, @fooの要素のリファレンスとその値出力 print $_. " : ". "$_n" for @hoge,@foo; print "-----n"; # 配列を値渡し output3(@hoge,@foo); sub output3 { # @_ の要素のリファレンスとその値出力 print $_. " : ". "$_n" for @_; } こういうことです。 このコードでは、引数とする配列 @hoge, @foo の各要素のリファレン スとその値、それからサブルーチンの中では引数を格納した @_ の各要 素のリファレンスとその値を出力します。 サブルーチンには 2 つの配列を値渡し (つまりリファレンスではない) をしています。
  • 19.
    SCALAR(0x7f8dde803268) : hoge SCALAR(0x7f8dde803418): fuga SCALAR(0x7f8dde828928) : foo SCALAR(0x7f8dde828940) : bar SCALAR(0x7f8dde828970) : baz ----- SCALAR(0x7f8dde803268) : hoge SCALAR(0x7f8dde803418) : fuga SCALAR(0x7f8dde828928) : foo SCALAR(0x7f8dde828940) : bar SCALAR(0x7f8dde828970) : baz 実行結果は次のとおり。 サブルーチンの引数では値渡し (通常は値がコピーされる ) をしてい るにもかかわらず、サブルーチン内の @_ が格納しているデータは、も との配列が持っている各要素 (スカラデータ) と同じアドレスをもって います。
  • 20.
    SCALAR(0x7f8dde803268) : hoge SCALAR(0x7f8dde803418): fuga SCALAR(0x7f8dde828928) : foo SCALAR(0x7f8dde828940) : bar SCALAR(0x7f8dde828970) : baz ----- SCALAR(0x7f8dde803268) : hoge SCALAR(0x7f8dde803418) : fuga SCALAR(0x7f8dde828928) : foo SCALAR(0x7f8dde828940) : bar SCALAR(0x7f8dde828970) : baz つまり、 サブルーチンへの引数は、それがリファレンスでなくても (値渡しで も)、内部的には参照渡しがされていることがわかります。 これを、@_ の各要素は引数のスカラデータの「エイリアスとして扱わ れる」といいます。
  • 21.
  • 22.
    use Data::Dumper; my %hash= (key1 => 'val1', key2 => 'val2'); my @arry = qw/elm1 elm2 elm3/; output4(%hash, @arry); sub output4 { # @_ のデータ構造を出力 print Dumper @_; } ためしにハッシュと配列を引数として渡してみると、 $VAR1 = [ 'key2', 'val2', 'key1', 'val1', 'elm1', 'elm2', 'elm3' ]; @_ のデータ構造は次のような単純なリストデータとして扱われます。
  • 23.
  • 24.
    use Data::Dumper; my %hash= (key1 => 'val1', key2 => 'val2'); my @arry = qw/elm1 elm2 elm3/; # 引数はリファレンス output4(%hash, @arry); sub output4 { # @_ のデータ構造を出力 print Dumper @_; } ハッシュと配列のリファレンスを引数として渡すと、 $VAR1 = [ { 'key2' => 'val2', 'key1' => 'val1' }, [ 'elm1', 'elm2', 'elm3' ] ]; @_ のデータ構造は、元のデータ構造を保持します。
  • 25.
    まとめ • サブルーチンへの引数は、内部的に参照渡しされる (エイリアスとし て扱われる)ので、引数から @_ へのデータコピーは行われない。 • ただし、配列やハッシュなどのデータ構造は維持されないので、サブ ルーチン内で引数のデータ構造をそのまま扱いたい場合はリファレン ス/デリファレンスを活用すると便利 (というかスタンダード)。 • なお、エイリアスとして扱われるデータを変更すると、それはリファ レンスと同じく参照渡しされたデータなので、引数となった元のデー タを直接変更する結果になる。 • サブルーチンの他、foreach,map,grep などでも $_ が扱われるが、 それらも同様にエイリアスなので、元のデータに影響を与えたくない 場合は foreach my $tmp (LIST){…} などとして明示的なデータのコ ピーを行う手法が一般的。
  • 26.
  • 27.
    Perl ユーザは、ある程度のレベルから「リファレンス/デリファレン ス」および無名配列リファレンス ([ ] )、無名ハッシュリファレン ス ( { } )、無名コードリファレンス ( sub{ } ) などを空気のよう に扱うようになります。 また、サブルーチンの応用形である各種ライブラリ・モジュールの作 成・利用などでは、任意のデータ構造を適切に扱うために「リファレ ンス/デリファレンス」の利用がほとんど必須になっています。
  • 28.
    このため、彼ら (Perl 中級者以上)のいう「配列」や「ハッシュ」( あるいはサブルーチン ) は、暗黙的にそれがリファレンスであること が多いようです。
  • 29.
    ですから、 • 初心者の立場から: それが素朴な配列・ハッシュなのかそれともリ ファレンスなのかをしっかり確認しましょう。リファレンスの理解 があやふやなら他を差し置いてでもリファレンスの理解を確実にし ておいたほうが後々の理解がスムーズになるはずです。 •中級者以上の立場から: それが素朴な配列・ハッシュなのかそれと もリファレンスなのかをしっかり説明しましょう。リファレンスの 理解があやふやなら他を差し置いてでもリファレンスの理解を確実 にしておいたほうがいいと思うよ (ニッコリ) と伝えてあげるとコ ミュニケーションがスムーズになるはずです。
  • 30.