┌──────────────────┐
│Perl入門 〜構造化プログラミング編〜 │
│(4)サブルーチン:引数/戻り値     │
│2005/10/19             │
└──────────────────┘

[▲Perl Home▲]


●サブルーチンへの引数/戻り値(1)

 ◆リスト「@_」
 
  ・前回は変数の有効範囲について話をしましたが、今回はそれらの値を関数間でや
   り取りする方法について考えて行きたいと思います。
  
  ・サブルーチン呼び出しの際、サブルーチン名の横に記述された値/変数は特殊な
   リスト変数@_によって、サブルーチンのブロックへ引き渡されます。
  
    ┌──────────────────────────────────┐
     #!/usr/local/bin/perl -w
     #注意!! このコードは実用的ではありません!!
    
     use strict;
    
     { # クロージャ : $a,$bの有効範囲を制限するために使いました
       my $a = 10;
       my $b = 20;
      
       print "実行前 : \$a = $a, \$b = $b\n";
       &SubTwice($a, $b);
       print "実行後 : \$a = $a, \$b = $b\n";
     }
    
     sub SubTwice {
       for (my $i=0; $i<@_; $i++) { # @_リストは引数の値を持つ
         print "サブルーチン内_引数値 [$i] = $_[$i]\n";
         $_[$i] *= 2; # 2倍に
         print "サブルーチン内_処理後 [$i] = $_[$i]\n";
       }
     }
     ──────────────────────────────────
     [実行結果]
     実行前 : $a = 10, $b = 20
     サブルーチン内_引数値 [0] = 10
     サブルーチン内_処理後 [0] = 20
     サブルーチン内_引数値 [1] = 20
     サブルーチン内_処理後 [1] = 40
     実行後 : $a = 20, $b = 40   # 値が変わってる!!
    └──────────────────────────────────┘

  ・上記のコードは、引数$a,$bの値を、サブルーチンSubTwice内で受けた後、2倍に
   する処理をしています。ここで注意して欲しいのは、サブルーチン実行後、その
   値を呼び出し側へ返してもいないのに、値が変化しているということです。
  
  ・つまり「@_」リストの重要な性質として、「引数のリファレンスを渡す」という
   ものがあります。このままだと、呼び出し側とサブルーチン側で処理と変数の分
   離ができないので、構造化記述が苦しいですね。
  
  ・そこで変数をサブルーチン内で独立に扱うため、前回話をした「my関数」を使う
   ことになります。以下に、その場合の例を示します。

    ┌──────────────────────────────────┐
     #!/usr/local/bin/perl -w
     use strict;
    
     { # クロージャ : $a,$bの有効範囲を制限するために使いました
       my $a = 10;
       my $b = 20;
      
       print "実行前 : \$a = $a, \$b = $b\n";
       &SubTwice($a, $b);
       print "実行後 : \$a = $a, \$b = $b\n";
     }
    
     sub SubTwice {
       my ($a, $b) = @_;
      
       print "サブルーチン内_処理前 \$a = $a\n";
       $a *= 2;
       print "サブルーチン内_処理後 \$a = $a\n";
      
       print "サブルーチン内_処理前 \$b = $b\n";
       $b *= 2;
       print "サブルーチン内_処理後 \$b = $b\n";
     }
     ──────────────────────────────────
     [実行結果]
     実行前 : $a = 10, $b = 20
     サブルーチン内_処理前 $a = 10
     サブルーチン内_処理後 $a = 20
     サブルーチン内_処理前 $b = 20
     サブルーチン内_処理後 $b = 40
     実行後 : $a = 10, $b = 20
    └──────────────────────────────────┘


 ◆サブルーチンから値を戻す

  ・サブルーチン内の変数を呼び出し側と独立化できるようになりました。すると、
   今度はサブルーチンの処理結果を戻す方法について考えなければなりません。こ
   れには組み込み関数の「return()」を使用します。
  
  ・return()は引数を以下のように戻すことができます。
  
     return($a); # スカラー値 $a を戻します
     return(@a); # リスト @a を戻します
     return(%a); # ハッシュ %a を戻します
     return();  # undefを戻します
     
     return($a, $b); # ($a, $b)をリストとして戻します
     return(@a, @b); # (@a, @b)が平坦化された単一のリストを戻します
     return(%a, %b); # (%a, %b)が平坦化された単一のハッシュを戻します
  
  ・いくつかの記述例を示してみます。

    ┌──────────────────────────────────┐
     #!/usr/local/bin/perl -w
     use strict;
    
     {
       my $sc1 = &SubRetSc1();
       print "\$sc1:$sc1\n";
      
       my ($sc20, $sc21) = &SubRetSc2(); # リストで受ける
       print "\$sc20:$sc20, \$sc21:$sc21\n";
      
       my %hash2 = &SubRetHash2();    # 平坦化ハッシュ
       foreach my $name (sort(keys(%hash2))) {
         print "key:$name, val:$hash2{$name}\n";
       }
     }
    
     sub SubRetSc1 {
       my $ret_sc1 = 'sc1_val';
      
       return($ret_sc1);
     }
    
     sub SubRetSc2 {
       my $ret_sc20 = 'sc20_val';
       my $ret_sc21 = 'sc21_val';
      
       return($ret_sc20, $ret_sc21);
     }
    
     sub SubRetHash2 {
       my %a = ('a0'=>'val_a0', 'a1'=>'val_a1');
       my %b = ('b0'=>'val_b0', 'b1'=>'val_b1');
      
       return(%a,%b);
     }
     ──────────────────────────────────
     [実行結果]
     $sc1:sc1_val
     $sc20:sc20_val, $sc21:sc21_val
     key:a0, val:val_a0
     key:a1, val:val_a1
     key:b0, val:val_b0
     key:b1, val:val_b1
    └──────────────────────────────────┘

  ・カンの良い方はすでに気付かれていると思いますが、複数のリスト(@)やハッシュ
   (%)を戻した場合、それは「平坦化」されます。このままでは別々のリスト/ハッ
   シュとして値を戻すことができません。
  
  ・このことはサブルーチンが引数を受け取る場合も同様です。このようなときには
   以前話をしたリファレンスを使用します。


●サブルーチンへの引数/戻り値(2)

 ◆リファレンスの使用
 
  ・さて、リストやハッシュ等の構造化データを、サブルーチンと複数個やり取りす
   るにはリファレンスを使用します。リストがサブルーチンはリストを経由するこ
   とは変わらないので、リストの各要素を構造化データのリファレンスとする方法
   を取ります。
  
  ・下記例を見て頂くのが手っ取り早いでしょう。

    ┌──────────────────────────────────┐
     #!/usr/local/bin/perl -w
     use strict;
    
     {
       my @lista = ('a0', 'a1', 'a2');
       my @listb = ('b0', 'b1', 'b2');
      
       my ($rac, $rbc) = &ChangeList(\@lista, \@listb);
       #  |~~~~~~~~~        |~~~~~~~~~~~~~~~
       #  リファレンス受け     リファレンス渡し
      
       for (my $i=0; $i<@{$rac}; $i++) {
         print "$i : $lista[$i] : ";
         print $rac->[$i],"\n";
       }
       for (my $i=0; $i<@{$rbc}; $i++) {
         print "$i : $listb[$i] : ";
         print $rbc->[$i],"\n";
       }
     }
    
     sub ChangeList {
       my ($ra, $rb) = @_; # リファレンス受け
      
       # デリファレンスしてローカル処理
       my @la = @{$ra};
       my @lb = @{$rb};
      
       for (my $i=0; $i<@la; $i++) {
         $la[$i] .= '_a_change';
       }
       for (my $i=0; $i<@lb; $i++) {
         $lb[$i] .= '_b_change';
       }
      
       return(\@la, \@lb); # リファレンス戻し
     }
     ──────────────────────────────────
     [実行結果]
     0 : a0 : a0_a_change
     1 : a1 : a1_a_change
     2 : a2 : a2_a_change
     0 : b0 : b0_b_change
     1 : b1 : b1_b_change
     2 : b2 : b2_b_change
    └──────────────────────────────────┘

  ・上記の例では、サブルーチン内でデリファレンスし、実質的には「値渡し」とし
   ていますが、リファレンスのまま処理を続ければ、元のデータ処理が加えられる
   いわゆる「参照渡し」になります。


 ◆デフォルト値有りの名前付き引数
 
  ・特別な機能ではありませんが、サブルーチンの引数にハッシュを適用することで
   「名前付きの引数」を使うことができます。
   
     &SubTest (name=>'Taro', hobby=>'ski');
      :
     sub SubTest {
       my %hash = @_;
        :
     }
   
  ・これに加え、後から指定した「key+value」が有効になるハッシュの性質と、平
   坦化を利用すれば、デフォルト値有りの名前付き引数を実現できます。

    ┌──────────────────────────────────┐
     #!/usr/local/bin/perl -w
     use strict;
    
     {
       &SubParaHash(
         name => 'taro',   # 名前付き引数記述
         job => 'musician'
       );
     }
    
     sub SubParaHash {
       my %defval = ( # デフォルト値定義
         name => 'null',
         job  => 'office worker',
         hobby => 'ski', # デフォルト趣味がスキー(笑)
         sex  => 'man'
       );
      
       my %para = (%defval, @_); # 平坦化してハッシュ受け
                    # 後指定(引数)のKey+Valueが有効になる
      
       foreach my $buf (sort(keys(%para))) {
         print "$buf\t: $para{$buf}\n";
       }
     }
     ──────────────────────────────────
     [実行結果]
     hobby  : ski
     job   : musician
     name  : taro
     sex   : man
    └──────────────────────────────────┘


●リファレンスカウント

 ◆メモリデータの解放
 
  ・プログラミング一般の話ですが、プログラムは動作中に何らかのメモリ領域を確
   保します。例えば今回のようにサブルーチンを使用すれば、そのサブルーチンが
   呼び出されるた度に必要なメモリが確保されていきます。
  
  ・しかし、サブルーチンは呼び出し回数に制限が設けられている訳では無いので、
   ある回数以上サブルーチンを実行すると、メモリ資源を使い果たしてプログラム
   が落ちてしまいます(*1)。
  
  ・通常はこれを避けるために「有効範囲が終了した変数は解放する」という仕組み
   が入っています。これを一般にガベージコレクション(garbage collection)と呼
   びます(*2)。
  
  ・しかし、サブルーチンから抜けた後も必要なデータが存在します。C言語で言う
   ところのヒープ領域から確保するダイナミックなデータ等がそれに相当しますが
   この種データ解放の仕組みは言語によりかなり異なります(*3)。
  
  ・Perlでは幸運なことに、動的に確保したメモリでも、自動的に解放されます。つ
   まりプログラマは、メモリの解放を意識しなくても大丈夫です。
 
 
 ◆リファレンスカウント:ダイナミックなデータ確保
  
  ・ならば、サブルーチン間で必要とされる間はずっと有効になるダイナミックなデ
   ータを、Perlではどのように「確保」するのでしょうか。それにはリファレンス
   を用います。
  
  ・Perlでは有効範囲の終わった変数は自動的に解放されますが、そのデータがリフ
   ァレンスを介して文字通り参照されている間は、解放の対象から外されます。こ
   れをリファレンスカウントと呼びます。つまり、内部でデータがリファレンスさ
   れている回数をカウントしていて、それが0より大きいうちは消さないという仕
   組みです。
  
  ・説明が長くなってしまいましたが、下記の例を見ればわかると思います。

    ┌──────────────────────────────────┐
     #!/usr/local/bin/perl -w
     use strict;
    
     {
       my $ref0 = &SubRefCount(); # 1回呼び出してリファレンス受け
      
       my $ref1 = &SubRefCount(); # もう1回呼び出してリファレンス受け
                    # ただし$ref0とは別のデータになる
                    
       for (my $i=0; $i<@{$ref0}; $i++) {
         $ref0->[$i] =~ s/data/DATA/; # $ref0側のみ変更
         print "$i ref0 : ",$ref0->[$i],' <=> ';
         print "$i ref1 : ",$ref1->[$i],"\n";
       }
     }
    
     sub SubRefCount {
       # @datas は my でサブルーチン内のスコープ
       # 普通は消えてしまうが...
       my @datas = ('data0', 'data1', 'data2');
      
       # @datasのリファレンスを返している
       return(\@datas); # リファレンスを戻す
     }
     ──────────────────────────────────
     [実行結果]
     0 ref0 : DATA0 <=> 0 ref1 : data0
     1 ref0 : DATA1 <=> 1 ref1 : data1
     2 ref0 : DATA2 <=> 2 ref1 : data2
    └──────────────────────────────────┘

  ・上記コードの実行結果より
    + SubRefCount内で生成された @datas のデータは、サブルーチン終了後も消
     えていない。
    + 更に複数回呼び出した場合は、別のデータが生成される。
   ことがわかったと思います。
  
  ・この機能を使えば、データ読み込み/確保部についても容易にコードを構造化で
   きますし、リファレンスの使用範囲を工夫すれば、メモリ確保/解放の効率も良
   くなります。
  
  ・Perlの場合、このリファレンスカウントを用いてダイナミックに確保したデータ
   を容易に管理できるとともに、データ自身に対する要素の追加/削除すらもでき
   てしまうので、C++等に比べると(*4)このあたりのフレキシビリティは飛び抜け
   て高いということが言えるでしょう。
  
  ・どうでしょうか。そろそろプログラミング言語としてのPerlの良さを感じ始めて
   頂けているのではないかと思います。次回からは、他のPerlコードを利用するor
   利用させるための仕組みとして「package」に話を移して行きたいと思います。



(*1)これをプログラミングにおいて一般にメモリリークと呼びます。デバイス屋さんだ
  と「リーク電流」とか「refresh time」とかが頭に浮かんじゃうかもしれませんが
  ...(^_^;

(*2)ここでは、解放することを一般にgarbage collectionと書いてしまいましたが、サ
  ブルーチン/関数内の自動変数解放はgarbage collectionとは異なります。サブル
  ーチン/関数のスタック解放と同時に消えるので。

(*3)例えば懐かしのN88-BASICではERASE文で配列を解放していましたね。C++ではnewで
  確保したデータをdeleteで解放します。Perlでは、これが自動的に行われることに
  なります。

(*4)C++でもauto_prやSTLを使用することで、自由度は上がっていますが、その意味を
  理解するのに敷居が高い傾向にあると感じています。

[▲Perl Home▲]

Copyright(c)2005 Monpe
All Rights Reserved