Function.
関数(1999/2/2)
今度は関数というモノを使ってみましょうか。関数というとおそらく数学(三角関数や超越関数等々)を思い出す人も多いことでしょう。実際数学の関数と同じではあるのですが、数学の関数が式によって値をはじき出すのに対して、プログラミング言語で言うところの関数は単に数値をはじき出すだけにとどまらずに様々なことが出来ます。プログラミング言語での関数は今までこのページで取り扱ってきたプログラムと何ら変わるところはありません。関数もプログラムそのものです。先に関数の具体例からいってみましょうか。
例として以下のようなファイルを扱うプログラムがあったとします。
$message = "ファイルをオープン出来ませんでした\n";
if( !open( FILE1, 'abc.txt' ) )
{# メッセージをファイルにライト
open( ERROR, '>error.txt' );
print ERROR $message;
close( ERROR );# メッセージを表示
print $message;
exit;}
ファイル処理
close( FILE1 );
if( !open( FILE2, 'def.txt' ) )
{# メッセージをファイルにライト
open( ERROR, '>error.txt' );
print ERROR $message;
close( ERROR );# メッセージを表示
print $message;
exit;}
ファイル処理
close( FILE2 );
ファイルabc.txtをオープンして何かの処理を行いクローズ、次にファイルdef.txtをオープンし何かの処理を行ってプログラムは終了します。openは以前説明しましたが、その直前に付いている!記号を説明しましょう。この!記号は否定を表す記号です。ifの条件にopenが入っているのはファイルのオープンが成功したかどうかを調べようとしているからです。ifの条件としてopenを使っているのでファイルオープンが成功した場合にはifの条件が満たされることになります。しかしifの{ }の中を見れば判るとおり、今ifの{ }内を実行したい時というのはファイルオープンが成功した場合ではなく、例えばオープンしようとしたファイルが存在していない等の理由でファイルオープンに失敗した場合です。
ifの条件が満たされたかどうかの判定は普通のプログラミング言語ですと「真偽」という2つの状態によって行われます。ifに指定した条件が「真」という状態になっていると、それは条件が満たされたと判定されます。逆にifに指定した条件が「偽」という状態なっていると条件が満たされなかったと判定されます。つまりifは、指定した条件が「真」の状態になっている場合に{ }内を実行することになっています。openがifの条件として使われている場合、ファイルオープンが成功した時に「真」、失敗した時に「偽」の状態になります。ここで!記号の登場とあいなります。!記号はこの「真偽」の状態をひっくり返す機能があります。つまり「真」の状態に!記号が使われれば「偽」の状態に変わり、「偽」の状態に!記号が使われれば「真」の状態に変わります。
openはファイルオープン成功時に「真」となるので、このopenに!記号を付けることでファイルオープン成功時の「真」が「偽」に変わります。逆にファイルオープン失敗時の「偽」は!記号により「真」に変わります。こうすると、ファイルオープン成功時の「真」が「偽」に変わってからifの条件判定のふるいに掛けられるので条件は満たされなかったと判定されます。逆にファイルオープン失敗時の「偽」は「真」に変わってからifの条件判定のふるいに掛けられるので条件が満たされたと判定されます。「真偽」の状態を「論理値」という言い方をすることがよくあります。!記号はこの論理値をひっくり返すので「論理否定」等と呼びます。
次にキーワードexitですが、そのスペルが示すようにexitというキーワードに出会った時点でperlプログラムが終了します。たとえexit以降にプログラムが続いていたとしても、exitに来た時点でプログラムは実行を止めて終了します。
さて論理否定とexitの話が終わったところで関数の話に戻ります。2つのファイルオープン失敗時にifの{ }内が実行されるわけですが、ifの{ }内はどちらも全く同じ処理です。この全く同じ処理になっている部分に関数を使うことを考えてみます。早速次のプログラムを見て下さい。
if( !open( FILE1, 'abc.txt' ) )
{&error; # エラーメッセージ出力
exit;}
ファイル処理
close( FILE1 );
if( !open( FILE2, 'def.txt' ) )
{&error; # エラーメッセージ出力
exit;}
ファイル処理
close( FILE2 );
# エラーメッセージを出力する関数
sub error
{$message = "ファイルをオープン出来ませんでした\n";
# メッセージをファイルにライト
open( ERROR, '>error.txt' );
print ERROR $message;
close( ERROR );# メッセージを表示
print $message;}
新しいキーワードと記号が登場しています。まず最後の方にあるsub errorという部分ですが、このsub error以降が関数と呼ばれるモノです。関数には変数同様に名前を付けて他の関数や変数等と区別します。subというキーワードが関数であることを示しており、subのすぐ後に関数の名前を指定します。ここではerrorという名前の関数となっています。関数もプログラムそのものであると言いましたが、関数のプログラムは{ }内に書きます。さて、今度は&記号を見てみます。2つのifの{ }内に&errorという行があります。&errorはerrorという名前の関数を呼び出すことを意味します。
ここまで来たのでそろそろ関数についてもう少し詳しく話しておかないといけませんね。プログラムは通常始めから終わりまで順番に実行されていきます。しかし関数の呼出ということを行うと、この実行の順番が変わります。関数の呼出を行うと、プログラムの終わりに向かって順に実行していく前に関数へ寄り道をします。関数を実行し終わると寄り道を止めてまた元の順路に戻って実行を続けます。プログラムの方に戻りましょう。ファイルオープン失敗によりifの{ }内を実行しようとしますが、exitの行を実行する前に関数errorへ寄り道します。関数errorの{ }内ではエラーメッセージをファイルと標準出力の両方に出力します。関数errorの{ }内を全て実行し終わると、また順路に戻って次の行であるexitを実行します。
さて、処理の流れが変わる様子は判りましたでしょうか? でもこれだけで終わってしまうとイマイチ関数の使い道が判らないと思うので、今度は関数で引数というのを使ってみましょうか。引数は「ひきすう」と読んで下さい。字面からはおそらく何を言い表しているか判らないでしょう。関数を呼び出すときには一緒に情報を渡すことが出来ます。
if( !open( FILE1, 'abc.txt' ) )
{&error( 'abc.txt' ); # エラーメッセージ出力
exit;}
ファイル処理
close( FILE1 );
if( !open( FILE2, 'def.txt' ) )
{&error( 'def.txt' ); # エラーメッセージ出力
exit;}
ファイル処理
close( FILE2 );
# エラーメッセージを出力する関数
sub error
{$filename = $_[ 0 ];
$message = "ファイル'$filename'をオープン出来ませんでした\n";# メッセージをファイルにライト
open( ERROR, '>error.txt' );
print ERROR $message;
close( ERROR );# メッセージを表示
print $message;}
sub errorとそれを呼び出している箇所が前の例と異なっています。関数error内で変数が1つ増えています。$messageに代入している文字列を見ればすぐに判ると思いますが、出力するエラーメッセージ中にオープン出来なかったファイル名を入れようとしているらしいことが判ります。しかし関数errorはファイルabc.txtのオープン失敗時にも呼び出されますし、ファイルdef.txtのオープン失敗時にも呼び出されます。ですからそのままでは関数error側では実際にどちらのオープン失敗により呼び出されたのか判断がつきません。そこで引数を使います。$filenameへの代入の右辺を見て下さい。[ ]が使われているところから、何かの配列の先頭要素(添え字が0の要素)を代入しています。配列なのはとりあえずいいとして、その名前が妙ですね。アンダースコア1文字になっています。と、ここまで読んでピンと来た人は聡いですね。以前にも似たような変数を既に取り扱っています。思い出しましたか?
答えは組み込み変数です。以前出てきた組み込み変数は$/でした。今回は配列の@_です。今のケースでは配列@_が引数にあたります。引数とは関数呼び出し時に一緒に関数に渡される情報のことを言います。関数errorではオープンに失敗したファイル名が欲しいので、そのオープンに失敗したファイル名の文字列を引数として渡しています。この様子は関数errorを呼び出している箇所を見ると一目瞭然ですね。関数呼び出しに引数を使うには
&error( 'abc.txt' )
というようにします。この例では引数として文字列'abc.txt'を渡しています。引数は必ずしも文字列である必要はありません。数値でもOKです。また引数は1つだけとは限りません。必要ならカンマで区切っていくつでも渡すことが出来ます。
引数を渡す方は終わりにして、引数を受け取る側の方をもう少しちゃんと説明しましょう。関数内で引数を受け取るには組み込み変数@_を用います。@が付いているので当然ながらこれは配列です。さて、何故配列になっているのでしょう? ここでも、すぐにピンと来た人はやはり聡いです。配列になっている理由は配列になっている方が便利だからです。何故その方が便利かといえば、答えは関数に渡すことが出来る引数の個数が1つとは限らないからです。複数の引数を受け取る関数を例にしましょう。
&function( 'abc', 100 );
sub function
{( $argument1, $argument2 ) = @_;
print "最初の引数は$argument1\n";
print "次の引数は$argument2\n";}
こんな関数があったとします。配列@_を代入の左辺を見てください。この左辺のような書き方を何と呼ぶか覚えているでしょうか? だいぶ以前に触れましたがここで再度登場します。カンマで区切ったモノを( )で括ると、それはリストと呼ばれます。ここまでは以前説明しました。その時にリストはスカラーをカンマで区切って列挙し( )で括る、と説明しました。今の例ではリスト中にスカラー変数を並べています。代入の左辺にリストがある場合は、リスト中にスカラー変数や配列を入れておくことでそれらの変数に順番に代入を行うことが出来ます。今の例ですと$argument1には$_[ 0 ]、$argument2には$_[ 1 ]が代入されます。関数への引数を保持する配列@_には関数呼び出しで指定された引数が順番に代入されています。関数呼び出し時に指定された引数が左から右に向かって順に配列@_に代入されてから関数が呼び出されます。つまり文字列'abc'が$_[ 0 ]に、数値100が$_[ 1 ]に代入されて関数が呼び出されます。
引数を渡された関数側では、必要に応じてリストを用いてバラの変数に代入させることが出来るわけです。このようなリストと配列の使い方をしている時はよく、配列をリストに展開する、等という表現をします。リストを用いた配列の展開を例に挙げておきます。
( $a, $b, $c ) = @_; # 配列(@_)をスカラー($a、$b、$c)に展開
( @a ) = @_; # 配列(@_)を配列(@a)に展開
( $a, $b ) = ( $_[ 0 ], $_[ 1 ] ); # スカラー($_[ 0 ]、$_[ 1 ])をスカラー($a、$b)に展開
( $a, $b, @c ) = @_; # 配列(@_)をスカラー($a、$b)と配列(@c)に展開
( $a, $b, @c ) = ( $_[ 0 ], $_[ 1 ], $_[ 2 ], $_[ 3 ], $_[ 4 ] ); # スカラー($_[ 0 ]〜$_[ 4 ])をスカラー($a、$b)と配列(@c)に展開
こんなところでしょうか。最初の例では配列@_を3つのスカラー変数に展開しています。ここで、代入先のスカラー変数は3つしかないので、配列@_の最初の3要素だけがスカラー変数に代入されます。配列@_の4つ目以降の要素はどこにも代入されません。 2つ目の例は配列@_の代入先が同じく配列になっています。この場合は配列@_の全要素がそのまま配列@aに代入されます。つまり代入後の配列@aの要素数は配列@_の要素数と同じになります。 3つ目の例はスカラーからスカラーに代入しています。各スカラーは1対1対応で代入されます。4つ目の例では代入先がスカラーと配列の両方になっています。この場合$_[ 0 ]が$aに、$_[ 1 ]が$bに代入され、$_[ 2 ]以降の要素全てが配列@cに代入されます。従って代入後の配列@cの要素数は配列@_の要素数 - 2となります。最後の例はスカラーをスカラーと配列に代入しています。$_[ 0 ]が$aに、$_[ 1 ]が$bに、$_[ 2 ]〜$[ 4 ]が@cに代入されるので、代入後の配列@cの要素数は3になります。