Perl入門ゼミ

  1. Perl
  2. サブルーチン
  3. here

サブルーチンのリファレンス

サブルーチンのリファレンスとは、サブルーチンを指し示すもののことです。

# リファレンスを作成
my $twice_ref = \&twice;
# デリファレンスして呼び出し
my $twice_num = $twice_ref->($num); 
 
sub twice {
  return $_[0] * 2;
}

&twiceで、サブルーチンそのものを表現します。( @がつけられると配列、%がつけられるとハッシュというのと同じように、&がつけられるとサブルーチンを表します。) 普段は、twice($num) のようにサブルーチンを呼び出していますが、&twice( $num ) のように明示的に呼び出すこともできます。

サブルーチンへのリファレンスを表現するには、\&twice のようにします。サブルーチンを呼び出すには、$twice_ref->(1) のように、サブルーチンへのリファレンスをデリファレンスしてます。ハッシュや配列のデリファレンスによく似た記述です。

リファレンスについての詳しい解説は以下の記事をご覧ください。

無名サブルーチンへのリファレンス

sub{ } といった名前のないサブルーチンへのリファレンスを作成することができます。my $sum_ref = sub{ }; のように記述します。サブルーチンそのものが実行されるわけではなく、サブルーチンへのリファレンスが、$sum_ref に代入されます。

my $sum_ref = sub {
  my @nums = @_;
  my $total;
  for my $num (@nums){
    $total += $num;
  }
  return $total;
};

サブルーチンへのリファレンスを作成すれば、引数としてサブルーチンに渡すことができます。Perlでは、サブルーチンのリファレンスを利用して、サブルーチンにサブルーチンを渡すことができるのです。

また、サブルーチンへのリファレンスを使用すれば、選択的にサブルーチンを実行することができます。次回に具体例を見ます。

サブルーチンへのリファレンスを利用したポリモーフィズム

サブルーチンへのリファレンスを利用したポリモーフィズムを実現したサンプルです。

use strict;
use warnings;

my @nums = (1, 2, 3);

print "1: サブルーチンへのリファレンスを使って合計と平均を順番に求める。\n";

# サブルーチンへのリファレンスの# 配列を作成。
my @calc_funcs = (\&sum, \&average); 
                                       
for my $calc_func (@calc_funcs) {
  my $result = $calc_func->(@nums);
  print "結果: $result\n";
}
print "\n";

sub sum {
  my @nums = @_;
  my $total;
  for my $num (@nums) {
    $total += $num;
  }
  return $total;
}

sub average {
  return sum(@_) / @_;
}


print "2: サブルーチンへのリファレンスを使って選択的に処理する。\n";
my $how_to_calc = 'sum';
my $calc_func;

if ($how_to_calc eq 'sum') {
  $calc_func = \∑
}
else {
  $calc_func = \&average;
}

my $result = $calc_func->(@nums);

print "計算方法 : $how_to_calc\n" .
  "結果 : $result\n\n";


print "3: サブルーチンへのリファレンスとハッシュを使って選択的に処理する。\n";
$how_to_calc = 'average';
my %calc_table = (sum => \&sum, average => \&average);

# $calc_table{'average'} には、サブルーチンへのリファレンス
# が入っているので、-> 演算子でデリファレンスして
# サブルーチンを呼び出す。
$result = $calc_table{ $how_to_calc }->(@nums); 

print "計算方法 : $how_to_calc\n" .
  "結果 : $result\n\n";

変数に対してサブルーチンを順番に適用する

my @calc_funcs = (\&sum, \&average);
for my $calc_func (@calc_funcs) {
  my $result = $calc_func->(@nums);
}

ある変数に対してサブルーチンを順番に適用するには、サブルーチンへのリファレンスを利用します。サブルーチンへのリファレンスを配列に格納してforループで繰り返しサブルーチンを呼び出します。

変数とサブルーチンのイメージ

|----------|
|          |<-------- サブルーチン1
|          |
|  変数    |<-------- サブルーチン2
|          |
|          |<-------- サブルーチン3
|----------|

ある種類の引数に対して、順番にサブルーチンを適用していくイメージ。引数は固定で、サブルーチンが移り変わってゆくイメージ。

サブルーチンへのリファレンスを利用したポリモーフィズム

# if文で分岐する方法
my $how_to_calc = 'sum';
my $calc_func;

if ($how_to_calc eq 'sum') {
  $calc_func = \∑
}
else {
  $calc_func = \&average;
}

my $result = $calc_func->(@nums);

ポリモーフィズムとは、「同じ記述であるのに異なる関数が呼び出される」ということを意味する言葉です。オブジェクト指向では、「オブジェクトに応じて異なるメソッドが適用される」ということを意味する場合が多いです。

この例の場合は、 $calc_func->(@num) という記述は、$calc_func に何が代入されているかによって意味を変えます。\$sum が代入されている場合は、sum サブルーチンが呼びだされ、\$average が代入されている場合は、average サブルーチンが呼びだされます。

javaでは、オーバーロードとオーバーライドがポリモーフィズムを実現するための方法で、C言語では関数ポインタです。

ハッシュを利用した美しい方法

$how_to_calc = 'average';
my %calc_table = (sum => \&sum, average => \&average);
$result = $calc_table{$how_to_calc}->(@nums); 

ハッシュを利用した方法が関数リファレンスによるポリモーフィズムのもっとも洗練された形だとわたしは思っています。

選択的な処理であるのに、if文をいっさい使っていません。ハッシュのキーに対応したサブルーチンを呼ぶことで、処理を分岐させています。

サブルーチンでシグナルハンドラの実装

シグナルハンドラとは、シグナルをきっかけとして実行されるサブルーチンのことです。シグナルとはOSがプロセスに送る合図です。Ctrl + c は「現在実行中のプロセスにINTシグナルを送ってね。」とOSに依頼します。また、alerm関数は「現在実行中のプロセスにn秒後にLARMシグナルを送ってね」とOSに依頼します。

$SIG{INT} = sub {
  die "INTシグナルがこのプロセスに送られました。";
};

$SIG{ALRM} = sub {
  die "ALRMシグナルがこのプロセスに送られました。";
};

INTシグナルを捕らえるには、$SIG{ INT } に対して、サブルーチンへのリファレンスを代入します。この例では、無名サブルーチンを定義してそのリファレンスを代入しています。

シグナルに関する注意点はたくさんありますがここでは書きません。プロセス間通信について書くときに書きます。

イベント駆動型プログラミングの初歩的な実装

イベント駆動型プログラミングの初歩的な実装のサンプルです。

use strict;
use warnings;

# イベントハンドラ
my $text = "aIue!!!o";

# 引数に、イベントハンドラを渡す。
parse( 
  $text, 
  start_h => \&start,
  lc_h => \&found_lc,
  end_h => \&end,
);

# 文字列を解析するサブルーチン( イベント検知関数 )
sub parse {
  # テキストとイベントハンドラのハッシュを受け取る。
  my ($text, %handler) = @_;
  
  # 最初に呼ばれるハンドラ
  $handler{start_h}->();
  
  # 一文字づつ解析していく。
  for my $i (0 .. length($text) - 1) { 
    my $char = substr($text, $i, 1);
    
    # 小文字を発見したときにイベントハンドラを呼ぶ。
    # コールバック関数で利用できるように
    # 文字と位置を引数に渡す。
    if ($char =~ /[a-z]/) { 
      $handler{lc_h}->($char, $i); 
    }
  }
  
  # 最後に呼ばれるハンドラ
  $handler{end_h}->();
}

# イベントハンドラ( コールバック関数 )
sub start {
  print "文字列の解析が始まりました。\n";
}

sub found_lc {
  # 小文字を発見した場合に呼び出される。
  
  # 引数に文字とポジションを受け取れる。
  my ($char, $pos) = @_; 
                             
  print "$charの位置は、$pos です。\n";
}

sub end {
  print "文字列の解析が終わりました。\n";
}

(参考)substr関数

(1)イベントを検出するためのサブルーチン

# イベントを検出する関数の呼び出し。
parse( 
  $text, 
  start_h => \&start,
  lc_h => \&found_lc,
  end_h => \&end,
);

# イベントを検出する関数
sub parse {
  # テキストとイベントハンドラのハッシュを受け取る。
  my ( $text, %handler ) = @_; 
  
  # 最初に呼ばれるハンドラ
  $handler{start_h}->();
  # ...
}

イベント駆動型のプログラミングでは、「イベントを検出する処理」と「イベントが発生したときに実行される処理」を分離します。そのために、まずイベントを検出する処理をサブルーチンとして実装します。

引数には、ハッシュを使って、サブルーチンへのリファレンスを渡しています。このサブルーチンはイベントが発生したときに実行されます。

(2)イベントが発生したときに実行されるサブルーチン(コールバック関数)

sub start {
  print "文字列の解析が始まりました。\n";
}

sub found_lc {
  # 小文字を発見した場合に呼び出される。
  # 引数に文字とポジションを受け取れる。

  my ($char, $pos) = @_; 
                             
  print "$charの位置は、$pos です。\n";
}

sub end {
  print "文字列の解析が終わりました。\n";
}

イベントが発生したときに実行されるサブルーチンは、一般的には、コールバック関数と呼ばれます。ここに、イベントが発生したときに行う処理を書きます。

イベントを検出する関数で、コールバック関数を呼ぶときに引数を渡してあげると、コールバック関数で受け取って利用することができます。

サンプルコード

サブルーチンのサンプルコードです。

サブルーチンのリファレンス

サブルーチンへのリファレンスを作成するサンプルです。

use strict;
use warnings;

# サブルーチンへのリファレンス
# 接頭に\をつけてあげるとサブルーチンへの
# リファレンスになります。
# &twice が、twiceサブルーチン自体を指します。
print "1: サブルーチンへのリファレンス\n";
my $twice_ref = \&twice; 

# 呼び出すときは、
# $sub_ref->( arg1, arg2 ) 
# のように呼び出します。
# 引数がいらないときは、
# $sub_ref->() です。
my $num = 1;
my $twice_num = $twice_ref->($num); 

print "$num の2倍は、$twice_numです。\n\n";

sub twice {
  return $_[0] * 2;
}


# sub{ } という名前を持たないサブルーチンへのリファレンスを$sum_refに代入。
print "2: 無名サブルーチンへのリファレンス\n";
my $sum_ref = sub {
  my @nums = @_;
  my $total;
  for my $num (@nums) {
    $total += $num;
  }
  return $total;
};

my $total = $sum_ref->(1, 2, 3);
print "1,2,3の和は$totalです。\n\n";

シグナルハンドラ

シグナルハンドラを使ったサンプルです。

use strict;
use warnings;

print "1: シグナルハンドラでINTシグナルを捕獲する。\n";

$SIG{INT} = sub {
  die "INTシグナルがこのプロセスに送られました。";
};

# 無限ループ
while(1){ };

# INTシグナルを実行中のプロセスに送るには、Ctrl + c とします。
use strict;
use warnings;

print "2: シグナルハンドラでALRMシグナルを捕獲する。\n";

$SIG{ALRM} = sub {
  die "ALRMシグナルがこのプロセスに送られました。";
};

# 2秒後に、ALRM シグナルを実行中のプロセスに送る。
alarm 2; 

# 無限ループ
while( 1 ){ };