Perl で“るんるん”関数プログラムの巻


Perlに限らず、UNIXなどで良く用いられているスクリプト言語はバッチ処理などによく用いられているため、余り関数プログラムがされていない実状があります。しかし、やろうと思えばしっかり関数プログラミングもできます。

C との違いは関数の宣言部がないことで、関数の呼び出しと関数の定義の2つ操作が必要になります。また、C と異なりスコープが無いので関数はすべてグローバルになっています。つまり、サブルーチンの中で関数の定義はできないわけです。サブルーチンの名称は注意してきめませう。


関数の呼び出しと定義

関数の呼び出しと定義を例を交えて解説します。時刻を0時からの秒に直す関数 time2sec を例に取ります。

関数の呼び出し

$sec = &time2sec($time);

関数を呼び出す場合は関数名の前に“&”をつければ実行されます。引数は関数名の後ろにカッコ( )をつけてカンマで区切って書きます。複数の変数を持つときは、&init(a,b,c)のようにして呼び出せます。

関数の定義

Perl には、C 言語におけるプロトタイプ宣言がありません。うーん、すばらしい(本当か?)。ちなみに、関数は先頭に持ってきても、最後尾においてもどちらでも構いません。私は C のスタイルが好きなので、最後尾においています。「おれは Pascal じゃあ〜」というひとはサブルーチンを先頭に置きましょう。

実際に関数を定義する例を見てみます。

sub time2sec{

    local($sec , $min , $hour);

    ($hour , $min , $sec ) = split(/:/,$_[0]);

    $min = $hour * 60 + $min;

    $sec = $min * 60 + $sec;

    $sec;

}

関数名の前に sub をつけます。そうすることによって、「ここから関数 time2sec の定義が始まりますよ」とスクリプトに教えているわけですね。

local変数

2行目を見ると local と言う命令があるのがわかると思います。これは括弧で括られた変数はローカル変数で、関数内にしかスコープがありませんよと言う意味です。

戻り値

戻り値は一つだけ返す事ができます。一つといっても、配列や連想配列を用いると複数のデータを返すことができます。それ以外の方法としてはグローバル変数として値を返す(というか、書き換える)しかないです。

引数

実引数は $time です。これが関数内でどのように処理されるかを考えてみましょう。引数は関数の中にわたると書かれた順に“@_”という配列に格納されます。ですから、それぞれの要素は渡した順に $_[0] , $_[1] , $_[2] ,....といったように利用できます。今回の例では実引数が1つなので、関数内では $_[0] だけが使用されています。

ローカル変数宣言

変数をローカルで宣言するには “local($sec , $min , $hour);” のようにサブルーチンの定義部の先頭につけてやれば良いです。

通常、関数を定義する場合にローカル変数を利用するのをお勧めします。ローカル宣言すると、その関数内の変数は、今までに出てきた変数と無関係になります。逆にいうと、ローカル宣言しないものはグローバル変数になってしまうので、注意が必要です。今まで出てきた変数などもそのまま使用できますが、引数として渡した方がモジュール性は格段にあがります。

ローカル宣言と同時に引数を渡すこともできます。具体的には次の2つのサンプルを見てください。

サンプル1

sub time2sec{

    local($sec , $min , $hour);

    ($hour , $min , $sec ) = split(/:/,$_[0]);

    $min = $hour * 60 + $min;

    $sec = $min * 60 + $sec;

    $sec;

}

サンプル2

sub time2sec{

    local($hour , $min , $sec ) = split(/:/,$_[0]);

    $min = $hour * 60 + $min;

    $sec = $min * 60 + $sec;

    $sec;

}

 2つのサンプルのうち、サンプル1が宣言と値の引き渡しを別々にやっているもの。サンプル2は宣言と値の引き渡しを同時にやっているものです。最初のうちはサンプル2の方がわかりにくいと思いますが、シンプルなだけ、慣れると楽です。私はなるべくローカル変数の宣言と値の引き渡しを同時にやるほうをお勧めします。

大沢流ローカル宣言と値の受け渡し

 大沢流と勝手に名乗っていますがなんてこたない、私がお勧めしているローカル宣言の方法です。ポイントは2つあって、

です。まあ、例として第1引数から第2引数までの数字の合計を返す関数 sum を見てみましょう。

呼び出し部

定義部


sub sum{

    local($x , $y) = @_;

    local($i , $return);

    

    for($i = $x , $return = 0 ; $i <= $y ; $i++){

        $return +="$i;" 

    } 

    $return; 

}

「ローカル宣言を引数とそれ以外で別々に行なうほうが分かりやすいかなぁ」という、それだけのお話です。


配列を戻り値や引数にする

戻り値が1つしかないと関数としては不便ですね。そんな場合は、配列を戻り値にしちゃいましょう。簡単にできます。以下に自分で作ったreverse関数の例を示します。

サンプル

#! /usr/local/bin/perl



@a = (0,1,2,3,4,5,6,7);



@a = &my_reverse(@a);



print "@a\n";



#以下、サブルーチン

sub my_reverse{

    local(@input) = @_;

    local(@return , $buff);



    while(@input){

        $buff = pop(@input);

        push(@return , $buff);

    }

    @return;

}

実行結果

    7 6 5 4 3 2 1 0

この例では引数として配列を渡しています。このように引数を配列にする事もできますが、配列を引数として渡しても、サブルーチンの内部では一元的に配列@_として値が渡されるため、注意が必要です。これについては次項で解説しましょう。

勘の良い人はもうお分かりかとも思いますが、Perlでいう配列とは何も@で始まるものだけではありません。変数 $a , $b , $c があった場合は ($a , $b , $c)も配列として使用できましたね。ですから Perl の関数は実質複数の戻り値を持つことができます。

配列と変数を引数にする場合

 引数は、渡した順番に一元的な配列に格納されます。具体的に言いますと&f($a,$b,$c,$d)と関数を呼び出した場合は$_[0] = $a , $_[1] = $b , $_[2] = $c , $_[3] = $d , となります。ここまではよいでしょうか?では2択クイズです。

<問題>

配列を引数として用いた場合、

  1. &f(@array,$a,$b);
  2. &f($a,$b,@array);

どちらが正しい呼び出し方でしょう?受け取る側の都合を考えるとわかります。

どっちでもできないことないけど、常識的に考えれば 2 が正解なのは分かると思います。引数として渡される段階で1つの配列として渡されるわけですから、この配列を引数として再分割することを考えると、2の方が簡単でしょう。

そこまで聞いても納得のいかない人のために、この引数を受け取る側の処理を書いてみましょう。

1の場合

sub f{

    local($b) = pop(@_);

    local($a) = pop(@_);

    local(@array) = @_;



    以後は処理・・・・

}

2の場合

sub f{

    local($a , $b , @array) = @_;



    以後は処理・・・・

}

ね、2の方が簡単でしょ。

複数の配列を引数にする場合

複数の配列を引数にする場合は2つの配列の切れ目をどうやって作るかを考える必要があります。いくつか考えることができますが、

なんかが考えられるのですが、誰かいい方法を考えてくれ〜


連想配列を引数や戻り値にする

連想配列も返すことができます。もちろん引数だって連想配列を渡すことも可能です。