Perl入門ゼミ

テキスト処理、Linuxサーバー管理、Web開発ならPerl
  1. Perl
  2. サブルーチン
  3. here

サブルーチンの動的な生成

Perlでは、サブルーチンを動的に生成することもできます。

関数ジェネレータ

関数ジェネレータとは、サブルーチンを生成するためのサブルーチンのことです。関数ジェネレータは、動的にサブルーチンを生成したいときに用いられるテクニックのひとつです。

一般的な名称にしたがって関数ジェネレータと呼びますが、Perlの用語でいえばサブルーチンジェネレータのことです。この解説では、関数もサブルーチンも同じ意味です。

関数ジェネレータの作成と呼び出し

# 呼び出し
my $func = func_generator();
$func->();

# 定義
sub func_generator {

  my $func = sub {
    print "これは、実行時に生成されたサブルーチンです。\n";
  };
  # サブルーチンへのリファレンスを返す。
  return $func; 
}

関すジェネレータを作成するには、無名サブルーチンを作成して、そのリファレンスを、戻り値として返します。

関数ジェネレータを呼び出す( func_generator() )と、生成されたサブルーチンへのリファレンスが戻りち値として取得できます。生成さえたサブルーチンのリファレンスをデリファレンスして( $func->() )して、サブルーチンを呼び出します。

少しだけ異なった複数のサブルーチンを生成する

# 関数ジェネレータ呼び出し
my $create_safe_msg = message_factory("is safe."); 
my $create_danger_msg = message_factory("is danger."); 

# 生成したサブルーチンの呼び出し
my $safe_msg1 = $create_safe_msg->("cat"); 
my $danger_msg1 = $create_danger_msg->("mamushi");

# 伝えるメッセージを指定できるサブルーチンを作成する関数ジェネレータ
sub message_factory {
  my $message = shift; 
  
  return sub {
    my $word = shift;
    return $word . ' ' . $message; 
  }
}

関数ジェネレータで引数を受け取って、その引数を生成するサブルーチンの中で利用することができます。こうすることで、少しだけ異なったサブルーチンを複数生成することができます。

型グロブを使ってサブルーチンの名前を動的に決定する

型グロブを使ってサブルーチンの名前を動的に決定するサンプルです。

無名サブルーチンに名前をつける

 *sum = sub {
  return $_[0] + $_[1];                         
};

型グロブを利用することで、サブルーチンに別名をつけることができます。左辺は型グロブ( *name )、右辺はサブルーチンへのリファレンス( \&subroutin )にします。

今回の例の場合は、右辺は無名サブルーチンへのリファレンスになっており、無名サブルーチンに別名をつけていることになります。

無名サブルーチンに名前をつけるくらいなら、最初から名前をつけておけばよいと考えるかもしれません。無名サブルーチンに名前をつけることの利点は、実行時にサブルーチンに名前を与えることができるということです。サブルーチン名を動的に決定することができます。

動的にサブルーチンに名前をつける

my $word = 'cat';
my $func_name = "${word}_repeat";

no strict 'refs';
*{$func_name} = sub {
  return "$word $word";                 
};

型グロブ名には、変数を使うことができます。$func_name に入る値によって、サブルーチン名が変化します。

*{$name} という記述は、use strict; を使用していると、エラーになります。no strict 'refs'として、シンボリックリンクの制限を無効にします。( ところで、*{ $name }という記述はなぜシンボリックリファレンスなんでしょうか? )今回の場合は、cat_repeat という名前を動的につけています。

もう少しよく書いてみましょう。

BEGIN {
  my $word = 'cat';
  my $func_name = "${word}_repeat";

  my $code = sub{
    return "$word $word"; 
  };
    
  no strict 'refs';    
  *{$func_name} = $code;
}

BEGINを使って、コンパイル時に動的にサブルーチン名を決定するようにしておきます。my $code = sub{ }; という記述でいったん無名サブルーチンへのリファレンスを変数に格納しておき、最後に *{ $func_name } = $code; として名前をつけます。このようにして、no strict 'refs' の有効範囲をできるだけ狭めておいたほうがよいです。

関数テンプレート

関数テンプレートとは、動的に複数のサブルーチンを作成するプログラム技法です。プログラムが始まる前にサブルーチンに名前をつけておくのではなくて、プログラムが始まってからサブルーチンに名前をつけます。

無名サブルーチンと型グロブを利用することで、サブルーチンを動的に生成し、動的に名前をつることができます。関数テンプレートを使えば、サブルーチン名に応じた処理を作成することができます。

関数テンプレートの書き方

my %numbers = (
  one => 1,
  two => 2,
  three => 3,
  four => 4,
);

for my $num (keys %numbers) {
  # 無名サブルーチンへのリファレンスを繰り返し作成
  my $code = sub {
    # 数の名前に対応した戻り値が返る
    return $numbers{$num}; 
  };
  
  # シンボリックリファレンスの制限を解除
  no strict 'refs';
  # 型グロブを利用して無名サブルーチンに名前をつける。  
  *{$num} = $code;
}

注目する点は、「サブルーチン名」と「サブルーチンの処理」に関連性があることです。サブルーチン名が、one であればそれに対応する $numbers{ 'one' },すなわち 1 が、戻り値として返却されます。

関数テンプレートを作成するには、まずforeach文にサブルーチン名の配列を渡して繰り返し呼びます。次に無名サブルーチンを作成して、関数名を利用した処理を行います。最後に、型グロブを利用して無名サブルーチンに名前をつけてあげます。

実際の利用としては、Class::Accessor などのモジュールで、アクセッサーの作成の部分で利用されています。

もう少しよく書いてみましょう。

BEGIN{
  my %numbers = (
    one => 1,
    two => 2,
    three => 3,
    four => 4,
  );

  for my $num (keys %numbers) {
    # 無名サブルーチンへのリファレンスを繰り返し作成
    my $code = sub {
      return $numbers{ $num }; # 数の名前に対応した戻り値が返る
    };
   
    # シンボリックリファレンスの制限を解除
    no strict 'refs';
    # 型グロブを利用して無名サブルーチンに名前をつける。 
    *{$num} = $code;
  }
}

BEGINブロックで囲って、コンパイル時に動的にサブルーチンを作成しておいたほうがよいです。そうしないと、この記述を行うまでは、サブルーチンを呼び出すことができません。

もっと良いのは、モジュールとして別ファイルに作成して、use を使ってコンパイル時に読み込む方法です。

サンプルコード

関数ジェネレーター

関数ジェネレーターのサンプルコードです。

関数ジェネレータをコードを使って解説します。

use strict;
use warnings;

print "1: 関数ジェネレータでサブルーチンを生成する\n";

my $func = func_generator();
$func->();
print "\n";

# 関数ジェネレータ
sub func_generator {

  my $func = sub {
    print "これは、実行時に生成されたサブルーチンです。\n";
  };

  # サブルーチンへのリファレンスを返す。
  return $func; 
}

print "2: 関数ジェネレータで複数のサブルーチンを生成する。\n";
# サブルーチンへのリファレンスが返る
my $create_safe_msg = message_factory("is safe.");
my $create_danger_msg = message_factory("is danger.");

print "'is safe' を与えて生成したサブルーチン\n";
# デリファレンスして呼び出す
my $safe_msg1 = $create_safe_msg->("cat");
my $safe_msg2 = $create_safe_msg->("dog");

print "${safe_msg1}\n${safe_msg2}\n\n";

print "'is danger' を与えて生成したサブルーチン\n";
my $danger_msg1 = $create_danger_msg->("mamushi");
my $danger_msg2 = $create_danger_msg->("suzumebachi");

print "${danger_msg1}\n${danger_msg2}\n\n";

# 伝えるメッセージを指定できるサブルーチンを作成する関数ジェネレータ
sub message_factory {
  # 関数ジェネレータの引数によって、生成するサブルーチンを動的に変更できる。
  my $message = shift; 

  return sub {
    my $word = shift;
    return $word . ' ' . $message;
  }
}

動的に生成したサブルーチンに名前をつける

型グロブを使って、動的に生成したサブルーチンに名前をつけるサンプルです。

use strict;
use warnings;

# サブルーチンの名前を実行時に決定する。
# 型グロブと無名サブルーチンを利用することで実現できます。

print "1: 無名サブルーチンに名前をつける。\n";
*sum = sub {
  # *sum は型ブログという機能で、別名を作成することができます。
  # ここでは名前のないサブルーチンにsumという名前をつけています。
  return $_[0] + $_[1];                         
};

print "1と2の和は、" . sum(1, 2) . "です。\n\n";

print "2: 動的にサブルーチンに名前をつける。\n";

my $word = 'cat';
my $func_name = "${word}_repeat";

no strict 'refs';
*{$func_name} = sub {
  # 無名サブルーチンに、cat_repeat 名前を実行時に
  # 与えることができます。
  return "$word $word"; 
};

my $repeat_word = cat_repeat();
print $repeat_word . "\n";

関数テンプレート

関数テンプレートを使ったサンプルです。

use strict;
use warnings;

# 関数テンプレート

print "1: 関数テンプレートを使って動的に複数のサブルーチンを作成する。\n";

# one というサブルーチンを呼んだら 1 が返却されるようにする。
# two というサブルーチンを呼んだら 2 が返却されるようにする。
my %numbers = (
  one => 1,
  two => 2,
  three => 3,
  four => 4,
);

for my $num (keys %numbers) {
  # 無名サブルーチンへのリファレンスを繰り返し作成
  my $code = sub {
    # 数の名前に対応した戻り値が返る
    return $numbers{ $num }; 
  };
  
  # シンボリックリファレンスの制限を解除
  no strict 'refs';
  # 型グロブを利用して無名サブルーチンに名前をつける。
  *{$num} = $code; 
}

print one(),two(),three(),four(),"\n";