関数プロトタイプ 基礎

というわけで、関数のプロトタイプを勉強します。
関数の定義の仕方を勉強している時に、引数の受け取り方も勉強しました。Perl では「可変長引数」(渡せる引数の数が決まっていない)がデフォルトで、引数はデフォルト配列 @_ で受け取るようになっているのでした。
そして、引数は「リストコンテキスト」で評価されるようになっています。例えば、以下のような関数を定義した場合……

use strict;
 
sub print_list {
  my @list = @_;
  my $i = 1;
  for (@list) { printf "%d: %s ", $i++, $_; }
  print "\n";
}
 
print_list "aaa", "bbb", "ccc";
 
my @arr = ("ddd", "eee", "fff");
print_list @arr;

print_list 関数は渡した引数をリストアップして番号付きで表示してくれる関数です。最初のprint_list呼び出しは、単純に3つの文字列を渡しています。それに対して、2つ目のprint_list呼び出しは一旦3つの文字列を配列変数 @arr に入れてから、@arr を引数として渡しています。
実行結果は以下の通りです。

1: aaa 2: bbb 3: ccc
1: ddd 2: eee 3: fff

最初の呼び出し方と、2つ目の呼び出し方は全く異なるにも関わらず、実行結果は同じものになりました。(^_^)
これはなぜかというと、「引数がリストコンテキストで評価される」という事に起因しているようです。実は、「配列変数はリストコンテキストで評価されると、暗黙的に中身がリストとして展開される」のです。
それを踏まえて、以下のコードを見てみましょう。

use strict;
 
my @arr = ("aaa", "bbb", "ccc");
push @arr, "ddd", "eee", "fff";
print "@arr";
aaa bbb ccc ddd eee fff

不思議ですね。(^_^) 「配列変数は展開される」はずなのに、push はちゃんと機能します。では、push を自作するにはどうすれば良いのでしょうか。第一引数で渡された配列変数を、「配列変数として」「暗黙的な展開を伴わずに」受け取るにはどうすれば良いのでしょう。


長い前置きでしたが(^_^;) これを実現する為に「プロトタイプ」という機能を利用するそうです。関数を定義する時に書く「sub 関数名」の後ろに「(引数の型)」を書くことで、Perl インタプリタに関数が受け取る引数の数・型を明示的に教える事ができます。
プロトタイプを使って push を自作してみました。

use strict;
 
sub my_push(\@@) {
  my $arr_ref = shift;
  my $i = @$arr_ref;
  while (@_) { $arr_ref->[$i++] = shift; }
  return $i;
}
 
my @arr = ("aaa", "bbb", "ccc");
my_push @arr, "ddd", "eee", "fff";
print "@arr";

my_push が関数名、その後ろがプロトタイプです。「\@」は「配列変数へのリファレンス」、「@」は「リスト」を表します。このように書くと、関数 my_push は第一引数に配列変数へのリファレンス、第二引数以降にリストを受け取る、という事を Perl コンパイラに教える事ができます。
Perl コンパイラは賢いので、リファレンスではなく配列変数を渡した場合は、自動的に(暗黙的に)「リファレンスに変換」してくれます。
実行結果は以下の通りです。

aaa bbb ccc ddd eee fff

ちゃんと動きますね。(^_^)
my_push 関数の中身を説明します。

  my $arr_ref = shift;
  my $i = @$arr_ref;

まず配列変数へのリファレンスを $arr_ref に受け取ります。そして $i に渡された配列変数の要素数を代入します。リファレンスをデリファレンスして、スカラーコンテキストで評価しています。配列変数はスカラーコンテキストで評価すると要素の数を返すのでしたね。(^_^)

  while (@_) { $arr_ref->[$i++] = shift; }

あとは、残りの引数を $arr_ref の配列変数の最後に追加していきます。「$i++」はC言語などでもよく使われる「インクリメント演算子」ですね。$i の値を1増やして、「増やす前の$iの値」を返します。「++$i」と書くと、$i の値を1増やして、「増やした後の$iの値」を返します。


段々と勉強した事を応用できるようになってきました。覚えた事が身になっているのを実感できて、とっても嬉しいですね。(^_^)