OOP(6) AUTOLOADで英文を作る

存在しないメソッドが呼ばれた場合、UNIVERSALに定義されている可能性があるというのは勉強しましたが、実は他にもエラーにならない可能性があります。
それが AUTOLOAD (オートロード)と呼ばれる機能で、呼ばれたメソッドが見つからない時、代わりに AUTOLOAD というメソッドが呼ばれます。つまり、AUTOLOAD メソッドを定義する事で、メソッドの呼び出しを漏れなくキャッチできるのです。
これを利用すると、メソッドを動的に(オンデマンドで)生成できます。呼び出されたメソッドの完全な名前は $AUTOLOAD に入っています。
AUTOLOAD を使って、面白いコードを書いてみました。

package Human;
 
sub new {
    my ($class, $name) = @_;
    bless \$name, $class;
}
 
sub DESTROY { }  # AUTOLOAD されないように
 
sub AUTOLOAD {
    my $self = shift;
    my %details = @_;
    my $text = "$$self is ";
 
    my @path = split "::", $AUTOLOAD;
    my $verb = $path[-1];
    $text .= "${verb}ing";
 
    for $pp (keys %details) {
        $text .= " $pp $details{$pp}";
    }
 
    $text .= ".\n";
    print $text;
}
 
package main;
$palmo = Human->new("Palmo");
$palmo->play(with => "Perl");
$palmo->sleep(at => "home");
$palmo->fly(in => "the sky", like => "a bird");

先に実行結果をお見せします。

Palmo is playing with Perl.
Palmo is sleeping at home.
Palmo is flying like a bird in the sky.

Human クラスについて順番に説明していきます。

sub new {
    my ($class, $name) = @_;
    bless \$name, $class;
}

まず、コンストラクタで自分の名前を $name で受け取って、そのリファレンス(\$name)を bless してオブジェクトにしています。

sub DESTROY { }  # AUTOLOAD されないように 

次に DESTROY メソッドですが、これは俗に「デストラクタ」と呼ばれるもので、オブジェクトがPerlによって破棄される時に呼ばれる特別なメソッドです。AUTOLOAD を定義すると、DESTROY メソッドまでオートロードされてしまうので、それを防ぐ為に空の DESTROY メソッドを定義しておいて、オートロードされないようにしています。

sub AUTOLOAD {

そして AUTOLOAD メソッドです。引数は通常のメソッドと同じように @_ に入っています。$_[0] はオブジェクトで、$_[1] 以降がメソッドに渡された引数となります。

    my $self = shift;

まず shift でオブジェクトだけを取り出しておきます。これで @_ には引数だけが残る事になります。

    my %details = @_;

引数を %details に代入しています。package main の方で play(with => "Perl") のように呼び出していますが、この引数をそのままハッシュ %details に代入している事になりますね。つまり、%details = (with => "Perl"); と同じ意味です。

    my $text = "$$self is ";

$text は print される文章です。最初にオブジェクトをデリファレンスして、コンストラクタに渡された名前を代入しています。Human->new("Palmo") で生成した場合は $text に "Palmo is " という文字列が入る事になります。

    my @path = split "::", $AUTOLOAD;
    my $verb = $path[-1];
    $text .= "${verb}ing";

split 関数は、文字列を指定した文字列や正規表現で分割して配列にする関数です。
$AUTOLOAD にはパッケージ名を含むメソッド名「Human::play」「Human::sleep」などが入っていますが、この「play」や「sleep」など、パッケージ名を除いたメソッド名を取得する為に "::" で配列 @path へ分割してから、配列の最後の要素を $verb に取得してます。
$text にさらに "${verb}ing" を連結しています。この中カッコ { } は、変数名を区切る為のものです。この中カッコを無くして "$verbing" と書くと、$verbing という存在しない変数を探してしまうのでエラーとなります。

    for $pp (keys %details) {
        $text .= " $pp $details{$pp}";
    }

%details のキーの配列に対して for (foreach)で繰り返しています。$pp に各キーが入りますが、このキーは前置詞(atやinなど)になっています。
$text に " with Perl" や " at home" などを付け加えていきます。

    $text .= ".\n";
    print $text;
}

最後にピリオドと改行を付けて出力しています。これで無事に文章ができました。

$palmo->play(with => "Perl");
$palmo->sleep(at => "home");
$palmo->fly(in => "the sky", like => "a bird");

play、sleep、fly メソッドはどこにも定義されていませんが、AUTOLOAD されて文章が出力されたわけです。面白いですね。(^_^)