[ ↑ INDEX ] [ ← PREV ] [ NEXT → ]

Lesson 8 サブルーチン


■1. サブルーチン

一まとまりの処理に名前を付けて「サブルーチン」として宣言すると,スクリプト内のどこからでも呼び出すことができるようになる。サブルーチンをうまく使うと,処理の流れがわかりやすくなり,修正も楽になる。

サブルーチンの宣言

サブルーチンの宣言は次の形式で行う。

   sub サブルーチン名 { 処理 }

サブルーチンの呼び出し

サブルーチンの呼び出しにはいくつかの方法があり,少しづつ振るまいが違うが,ここでは,基本的に,次の形式で呼び出すものとする。(詳しくはレファレンス等を参照すること。)

   &サブルーチン名 (引数)       #引数を指定する場合
   &サブルーチン名 ()           #引数がない場合

次のスクリプトを実行すると,"Hello![改行]" と出力する。

   -------------------------------------------------------
   &hello ();             #サブルーチン hello を呼び出し実行
   
   sub hello {            #サブルーチンの宣言,処理内容の指定
       print "Hello!\n";  #呼び出されると "Hello!\n" と出力
   }
   -------------------------------------------------------

   -------------------------------------------------------
   % perl -e '&hello (); sub hello { print "Hello!\n" }'
   -------------------------------------------------------

サブルーチンへのデータの受け渡し

データをサブルーチンに渡したいときには,( ) 内に引数として指定する。引数はリストとしてサブルーチンに渡される。したがって,コンマで区切って複数のデータを指定することができる。サブルーチン内では,このリストは @_ という特殊配列に収められる。

   &サブルーチン名 (引数0, 引数1, 引数2)
                     ↓
   サブルーチン内:  @_:    (引数0, 引数1, 引数2)
                     $_[0]:  引数0
                     $_[1]:  引数1
                     $_[2]:  引数2

配列からのデータの取り出しは,shift などの関数を使ったり,$_[0] のように要素を指定することで行う。

   --------------------------------------------------
   &hello ('morning');
   &hello ('afternoon');
   &hello ('evening');
   &hello ('night');

   sub hello {

      #リストの最初の要素()を取り出し $string に代入
      $string = shift @_;

      print "Good $string!\n";

   }
   --------------------------------------------------
      :ここで引き渡されるリストは1要素のみからなるもの

サブルーチンの返り値

サブルーチンは,最後に評価した式・変数などの値を値として返す。下のスクリプトのサブルーチン add は,最後に評価した「$x + $y」の値 (=11) を返り値として返す。

   --------------------------------------------------
   $value = &add (5, 6);
   print $value . "\n";            #$value の内容:11

   sub add {
      ($x, $y) = ($_[0], $_[1]);   #@_ の内容: (5, 6)
      $x + $y;                     #$x + $y の値が返り値
   }
   --------------------------------------------------

値を明示的に返すには,return を使う。

   --------------------------------------------------
   $value = &add (5, 6);
   print $value . "\n";

   sub add {
      ($x, $y) = ($_[0], $_[1]);
      return $x + $y;
   }
   --------------------------------------------------

次のように,リストを値として返すこともできる。(下のスクリプトは実行すると "2, 4, 6" と表示する。)

   --------------------------------------------------
   @doubled = &double (1, 2, 3);
   print join (", ", @doubled);

   sub double {
      foreach $i (@_) {
         push (@j, $i * 2);
      }
      return @j;
   }
   --------------------------------------------------

■2. sortの定義をサブルーチンで指定する

sort の定義をサブルーチン化すると,スクリプトが読みやすくなる。(「&サブルーチン()」の形式でない点に注意。)

   script/kwic3.plの一部
   -----------------------------------------------------------------
   foreach $line (sort { (split "\t", $a)[$sortkey]
                            cmp
                         (split "\t", $b)[$sortkey] } @lines) {
      $count++;
      printf "%5d | %-12.12s | %25s|%s|%-25.25s\n",
             $count, split ("\t", $line);
   }
   -----------------------------------------------------------------
   -----------------------------------------------------------------
   foreach $line (sort by_the_sort_key @lines) {
      $count++;
      printf "%5d | %-12.12s | %25s|%s|%-25.25s\n",
             $count, split ("\t", $line);
   }

   sub by_the_sort_key {
      (split "\t", $a)[$sortkey] cmp (split "\t", $b)[$sortkey]
   }
   -----------------------------------------------------------------

サブルーチンにすると,ソートの定義に手を加えていくのも容易になる。

   script/kwic4.plの一部
   -----------------------------------------------------------------
   sub by_the_sort_key {
      if ($sortkey == 1) {
         (join " ", reverse (split " ", (split "\t", $a)[$sortkey]))
            cmp
         (join " ", reverse (split " ", (split "\t", $b)[$sortkey]))
      } else {
         (split "\t", $a)[$sortkey]
            cmp
         (split "\t", $b)[$sortkey]
      }
   }
   -----------------------------------------------------------------

◇ スクリプトの実行

script/kwic4.pl を実行し,ソートキー (1: 左の文脈,2: キーワード,3: 右の文脈) の違いによりソートされる順序がどのように変わるか確認しなさい。また,スクリプトに手を加え,スペース以外の記号類を無視して文字列を比較するようにするにはどうしたらよいか考えなさい。

■3. 変数のスコープ

スクリプトも複雑になってくると,重複せずに変数名を付けるのが大変になってくる。my を使うと,ある範囲でだけ有効な変数を作ることができる。次の形式で宣言する。

   my 変数名

リストを使って一度に複数の変数を宣言することもできる。(括弧は必ず付ける。)

   my (変数名1, 変数名2, ...)

my で宣言された変数は,ブロック内でのみ有効で,ブロック外の変数と同じ名前であっても別の変数として扱われるので,変数名を管理するのが楽になる。

次の例は,サブルーチンの定義のブロックで my を使ったものである。

   --------------------------------------------------
   sub extract_key {
       my $key;
       $key = shift @_;
       $key =~ s/.*\|//;
       return $key;
   }
   --------------------------------------------------

このように変数 $keymy で宣言しておけば,ブロックの外に $key という名の変数があっても別の変数として扱われるので,名前の重複を気にする必要はなくなる。

my による宣言は,値の代入とまとめて1行で行うこともできる。次の例では,リストを使って,複数の変数をまとめて宣言している。

   --------------------------------------------------
   $value = &add (5, 6);
   print $value . "\n";

   sub add {
      my ($x, $y) = ($_[0], $_[1]);
      return $x + $y;
   }
   --------------------------------------------------

◇ スクリプトの実行

次のスクリプトを実行し結果を確認した後,各行の処理内容を確認しなさい。

   ord_num.pl
   -------------------------------------------------
   foreach $cardinal (1 .. 100) {
      $ordinal = &card2ord ($cardinal);
      printf "%5s\n", $ordinal;
   }

   exit;

   sub card2ord {
      my $num = shift @_;

      $num =~ s/(1[123])$/$1th/
         or
      $num =~ s/([0123])$/(qw|0th 1st 2nd 3rd|)[$1]/e 
         or
      $num .= 'th';

      return $num;
   }
   -------------------------------------------------
      qw||: クォート風演算子
      s/x/y/e: x を y を評価した結果で置換 (cf. m, s のオプション)

      参考:numeral.pl (Lesson 10)

□ 練習問題

練習問題A:必ずやること。

練習問題B:余裕があったらやってみよう。


[ ↑ INDEX ] [ ← PREV ] [ NEXT → ]