Perl Hackers Hub

第19回Acmeで広がるPerlの世界―CPANは愉快なジョークモジュールの宝庫(2)

まだまだたくさんあるよ! Acmeモジュール

ほかにもたくさんあるAcmeモジュールの中から、本当に一部ですがお勧めのものを表1にまとめました[3]⁠。ここに挙げたもののいくつかは、よそでも紹介されている有名なものです。ぜひ実際にインストールして動作を確かめてみてください。

表1 お勧めAcmeモジュール
モジュール名機能の概要
Acme::Bleach1回目の起動でソースコードを半角スペースとタブの2値に変換して「真っさら」⁠漂白=bleach)にする。2回目以降の呼び出しでは、内部で元のソースコードに戻すので元どおり動作する。モールス信号に変換するAcme::Morseなども同梱されている。最初のAcmeモジュール
Acme::Boomuseするとセグメンテーション違反(不正なメモリアクセス)を発生させエラーを引き起こす。同梱のテストファイルでは$SIG{SEGV}を使うことでエラーをトラップして動作確認をしている。ほかにAcme::Segvというモジュールもあるが、こちらはうまくエラーを起こしてくれないことが多い
Acme::Code::Policeこのモジュールがuseされたスクリプト内でuse strictをしていなかったら、そのファイルを削除する! まさにコードの治安維持者!
Acme::Code::FreedomFighter上述のAcme::Code::Policeがuse strictしていないファイルを削除しようしたとき、逆にAcme::Code::Policeモジュールを削除する! まさに自由のための闘士!
Acme::Damnblessされたオブジェクトを通常のオブジェクトに戻す。CPANモジュール中、厳密な意味でblessを解除する方法を提供しているモジュールはこれだけである。たいへん貴重な、Acmeじゃないパッケージの依存モジュールになっているAcmeモジュール
Acme::FizzBuzzuseするだけでFizzBuzz問題(整数の1から順に表示していき、3で割り切れるならFizz、5で割り切れるならBuzz、両方で割り切れるならFizzBuzzと表示するゲーム)を解決する
Acme::GodotSamuel Beckettの戯曲「ゴドーを待ちながら」よろしく、ゴドーを待ち続ける。内部的にはゴドーが来たかどうか判定し(常に偽値を返す⁠⁠、来なかったら約1年間sleepする。以上を繰り返す
Acme::MathProfessor::RandomPrime典型的な数学の教授が素数を取り上げる際に採用するアルゴリズムに基づいて、素数を返す。なんだかすごそうだが実は素数の17か23を返すだけ
Acme::MetaSyntacticfoo、barなどといった変数の名前を考えてくれる。膨大な数のカテゴリから選べるので便利
Acme::MorningMusumeアイドルグループ「モーニング娘。」のデータを取得できる。CPANにはほかにもAcme::MomoiroClover(ももいろクローバーZ)やAcme::PrettyCure(アニメ「プリキュア」シリーズ)などアイドルのデータ取得系モジュールが存在する
Acme::Math::PerfectChristmasTree高さ(cm)を与えると最適なクリスマスツリーの飾り付けに必要な道具(電飾とか頂上の☆とか)の長さを計算してくれる
Acme::POE::Treeコンソールにカラフルなクリスマスツリーが表示される。チカチカ光る
Acme::PricelessMethodsis_perl_installed(今使ってるマシンにPerlがインストールされているかどうか)やis_true_true(真値は真値であるか⁠⁠、universe_still_exists(宇宙はまだ存在しているか)などの極めて重要な真偽判定メソッドを提供する、真にプライスレスなモジュールである
Acme::Pythonuseするとソースコードが"hiss"という文字列(蛇(ニシキヘビ=python)「シャー」という音)に変換される。Acme::Bleach同様、2回目の起動以降は本来の動作をする。今年が巳年なので取り上げてみた
Acme::Sneeze::JPsneeze(くしゃみ)というメソッドを提供する。sneezeメソッドを呼び出すと参照アカウントが0にならない(誰かに噂されている)ので、ガベージコレクトの対象にならずメモリリークできる。Acme::Sneezeというモジュールもあり、こちらはAcme::Sneeze::JPとはまた違う動作をする
Acme::Takahashi::Methodソースコードを1行ごとに画面一杯に表示してくれる(みんな大好き「高橋メソッド⁠⁠。すべてのスライドが終了したらプログラムが実行される
Acme::Tpyo与えた文字列をtypo(タイプミス)されたものにして返す。このモジュールの名前もtypoしている
Plack::Middleware::Acme::PHPE9568F
34::D428::11d2::A769::00AA001ACF42
PHPに実装されている、クエリに"?=PHPE9568F34-D428-11d2-A769-00AA001ACF42"を付けるとロゴが表示される機能をPlackミドルウェアとして実現している
Plack::Middleware::Acme::Werewolf満月の日に狼人間がWebサイトにアクセスできないようにする(強制的に403Forbiddenを表示⁠⁠。もともとApache用モジュールだったAcme::Apache::WerewolfをPlackミドルウェアに移植したもの

Acme::MomoiroCloverパッケージ内にAcme::MomoiroClover::Zが同梱されている

AcmeモジュールからPerlの小技を学ぶ

さて、単におもしろモジュールの紹介だけでは寂しいので、ここからはAcmeモジュールを肴(さかな)に、@INCのフック、lvalueサブルーチン、そしてtie変数といった普段あまり使わない(かもしれない)Perlの機能について学んでいきます。

Acme::Anythingで学ぶ@INCのフック

Acme::Anythingというモジュールをuseすると、どんなモジュールでもuse(あるいはrequire)できるようになります。

リスト5で本来ならば存在しないモジュールYasukute OishiiIwashiをuseするとCan't locate Yasukute OishiiIwashi.pm in @INC ...というエラーが発生します。しかしこのソースを実行してみると、useに失敗しないで最後まで処理が進みます。

リスト5 Acme::Anythingを使ったコード
use Acme::Anything;
use YasukuteOishiiIwashi; # ← 実在しないモジュール
print "ok, this code didn't die.\n";
$ perl acme_anything.pl
ok, this code didn't die.

@INCにはライブラリまでのパスが入っている

ではこのモジュールはどのようなしくみで動いているのか、Acme::Anythingのソースコードを見てみますリスト6⁠。

リスト6 Acme::Anythingのソース(抜粋)
package Acme::Anything;

# 中略

push @main::INC, \&handler_of_last_resort; #  (1)

sub handler_of_last_resort {
    my $fake_source_code = '1';
    open my ($fh), '<', \$fake_source_code; #  (2)
    return $fh;
};

(1)で配列@INCにサブルーチンのリファレンスを挿入しています。perlはモジュールを呼び出すとき、そのモジュールの場所を配列@INCの先頭から順に調べていきます。ですから@INCには通常、モジュールまでのパスが入っています図4⁠。

図4 配列@INCの中身を確認
% perl -E'say $_ for @INC'
/home/makamaka/perlbrew/perls/perl-5.14.2/...
..

@INCにコードリファレンスを入れると……

しかし@INCには、パス代わりにサブルーチンのリファレンスやblessされたオブジェクトを入れることができます。その場合、perlはパスをチェックするのではなく、そのコード(blessされたオブジェクトならINCメソッド)を実行します。実行されたコードには自分自身とモジュール名が引数として渡されます。また、このコードはopenしたモジュールのファイルハンドルを返さなければなりませんリスト7⁠。詳しくはコマンドラインからperldoc -f requireしてみてください。

リスト7 一般化した@INCフック用サブルーチン
sub INC {
    my ( $self, $module_name ) = @_;
    my ( $filename = $module_name . '.pm' ) =~ s{::}{/}g;
    open( my $fh, '<', "$YOUR_LIB_PATH/$filename" )
        or die "Can't locate $filename in $YOUR_LIB_PATH";
    # %INC に何らかの値(通常はファイルまでのパス)を入れる
    $INC{ $filename } = "$YOUR_LIB_PATH/$filename";
    return $fh;
}

リスト6のAcme::Anythingのコードでもファイルハンドル$fhを返していますが、注意しておきたいのは、リスト6(2)の\$fake_source_code です。これは文字列'1'が代入されたスカラ変数のリファレンスです。openの第3引数にスカラリファレンスを渡すと、参照先の文字列を内容とするファイルとみなされます。つまり$fhは1という文字だけが書かれたファイルのファイルハンドルとして扱われます。perlが@INCのパスから指定されたモジュールを見つけられなかったとき、@INCの最後に挿入されたhandler_of_last_resortが実行され、"1"というソースコードのモジュールを読み込んだことになります。このおかげでuseしてもエラーにならないのです。

@INCを利用したAcmeモジュールたち

このように@INCに細工を仕込むAcmeモジュールを表2にまとめました。現在CPAN上にはほかに6個あります。ネタを仕込むのに好都合な機能であることがうかがえますね。

以上、Acmeモジュールから学ぶ@INCのフックでした。

表2 @INCを利用したAcmeモジュール
モジュール名機能の概要
Acme::Incorporated一定の確率でuseしたモジュールを何もしないモジュールや「品切れ」と表示するモジュールにすり替える。あるいはモジュール内にあるforやwhileの条件式を狂わせたりする。まさにAcme社!
Acme::Module::Authorsプログラムの実行終了時にuseしたモジュールの作者一覧が表示され、感謝を表明する
Acme::Nothingモジュールをuseしても何も起こらなくする
Acme::RemoteINCuseしたモジュールが存在しないときにFTP経由でダウンロード、インストールする
Acme::SpiderDamian Conwayの作ったモジュールをインストールできなくする(彼はクモが嫌い)
Acme::use::strict::with::prideuseしたモジュール内にuse strictとuse warningsを加える

Acmeモジュールから学ぶlvalueサブルーチンとtie変数

戻り値がないことを明示してみる

ご存じのようにPerlには戻り値に対して「コンテキスト」があります。演算子や式の戻り値がスカラとして期待されているのか、それともリストを期待されているのか、あるいは戻り値を要求しない(void)のかということですリスト8⁠。

リスト8 戻り値のコンテキスト
sub func_returns_list {
    return ('a', 'b', 'c');
}

@array = func_returns_list(); # @array => ('a', 'b', 'c')
$value = func_returns_list(); # $value => 'c'
func_returns_list();

さて、無効コンテキストのときはその旨をvoidと明示してみたくなりました。そこでAcme::Voidです。

use Acme::Void;

void context_ware_func();

自分が何をしているのか(=戻り値を必要としていないこと)がはっきりしてよいですね。

lvalueサブルーチン

このvoidは実際のところ何もしない関数なのですが、

void = do_something();

といった使い方もできます。関数voidに向けて値を返すのですが、その値はどこにいくこともなく消えます。しかしよく見ると引数としてではなく、直接関数に値を代入しようとしています。通常ですとシンタックスエラーになるのですが、lvalue(左辺値)という属性をvoidに与えることによって実現しています。

リスト9(1)で関数を定義するときにlvalue属性を指定します。(2)で左辺値として使う変数をreturnを付けずに返します。Perlの組込み関数substrなどは左辺値として使用できる関数の代表例でしょう。

リスト9 lavlueサブルーチンのサンプル
my $value;
sub func_returns_lvalue : lvalue { #  (1)
    $value; #  (2)returnは付けない
}

func_returns_lvalue() = 3; # $value = 3

Acme::Lvalue

lvalueサブルーチンを利用した、ずばりAcme::Lvalueというモジュールがあります。比較的最近リリースされたばかりなうえ、対応しているPerlのバージョンも5.16以降と、少々マイナーかもしれません。しかし、なかなか興味深いモジュールです。

リスト10を実行すると、まるで方程式のように、右辺の値になるように$xに適切な値が代入されます。

リスト10 Acme::Lvalueのサンプル
use v5.16; # 5.16.0 以降が必要
use Acme::Lvalue qw(:builtins);

my $x;

say sqrt(9); # => 3
sqrt($x) = 3;
say $x; # => 9

say reverse( 'abcde' ); # => edcba
reverse( $x ) = 'edcba';
say $x; # => abcde

どんなしくみ?

Acme::Lvalueはsqrtやreverseなどの組込み関数と同名のサブルーチンをエクスポートします。そのサブルーチン内では渡された引数をプロキシクラスにtieして返します。すると関数が右辺値として評価されるときはtieクラスのFETCHが呼び出されるのでこのときは本来の組込み関数と同じ処理を行います。逆にlvalueのときはtieクラスのSTOREが呼び出されるので、ここで本来の関数と逆の動作をするような処理を行っています。しかしこの説明だけだとわかりにくいですね。 実際にtieの使い方を見ていきましょう(tieについて詳しくはperldoc perltieをご覧ください⁠⁠。

変数をクラスにtieする

変数(スカラ、配列、ハッシュなど)を特定のクラスにtieすると、変数に値を代入するときや取り出すときに、好きな処理を噛ませることができます。

リスト11(1)でサブルーチンlval_funcはlvalue指定されていますので、右辺値としても左辺値としても呼べます。このサブルーチンの中の(3)で、変数$proxyをProxyクラスにtieして返します。

リスト11 tie変数を扱うコード
use Proxy;

sub lval_func : lvalue { # (1)
    # 変数をProxy クラスにtie します。
    # 引数として本来の処理と、lvalue 時の処理を渡します。
    tie my $proxy, 'Proxy', \$_[0],
            sub { sqrt $_[0] }, sub { $_[0] * $_[0] }; # (2)
    $proxy; # (3)
}

say lval_func(9); # (4)
my $x;
lval_func($x) = 3; # (5)
say $x;

(2)でtieの第3引数以降はオプションになります。ここではlval_funcに渡された引数$_[0] = $xのリファレンスと、2つのサブルーチンリファレンスを渡します。最初のサブルーチンはPerlの組込み関数sqrt(平方)を実行するだけのコードです。続く引数のサブルーチンはsqrtの逆の処理(自乗)を行います。

Acme::Lvalueの簡略コード

それではAcme::Lvalueのソースを単純化したコードをもとに、tie用のサブルーチンの設定方法を見ていきますリスト12⁠。このクラスはスカラ変数にtieされることが前提なので、最低限必要となるメソッドはTIESCALAR、FETCH、STOREの3種類になります。

リスト12 Acme::LValueを簡略化したコード
use strict;
use warnings;

package Proxy; # (1)

sub TIESCALAR { # (2)
    my ( $class, $value, $func, $lval_func ) = @_;
    my $self = {
        # lval_func に渡された値を保持
        value => $value,
        func => $func, # 本来の処理
        # lvalue として呼ばれたときの処理
        lval_func => $lval_func,
    };
    bless $self, $class; # (3)
}

sub FETCH { # (4)
    my ( $self ) = @_;
    # 右辺値として呼ばれたとき呼ばれる
    $self->{ func }->( ${$self->{ value }} ); # (5)
}

sub STORE { # (6)
    my ( $self, $value ) = @_;
    # 左辺値として呼ばれたとき呼ばれる
    my $lvalue = $self->{ value };
    $$lvalue = $self->{ lval_func }->( $value ); # (7)
}

TIESCALAR

リスト12(1)でProxyというpackage名を宣言しました。リスト12(2)のサブルーチンTIESCALARは、スカラ変数がtieされるときに自動的に呼び出されます。引数としてクラス名とオプションが渡されます。リスト11で見たように、オプションとしてスカラリファレンス、本来の処理を行うサブルーチンリファレンス、そして本来の処理の逆を行うサブルーチンのリファレンスを受け取っています。これらをハッシュリファレンスに入れて、普通のオブジェクトと同様にリスト12(3)でProxyパッケージにblessして返します。

FETCH

リスト12(4)のサブルーチンFETCHはtieされた変数から値が取り出されるときに呼ばれます。右辺値として呼び出されたときの動作になりますので、リスト12(5)で本来の処理を行わせ、戻り値をそのまま返します。

STORE

リスト12(6)のSTOREサブルーチンはtieされた変数に値が代入されるときに呼ばれます。左辺値として呼び出されたときの動作になりますので、リスト12(7)で本来とは違う処理を行わせます。結果は$selfの中にあるスカラリファレンスをデリファレンスして代入します。

右辺値として呼び出す

リスト11(4)では普通にlval_funcを右辺値として呼び出していますので、リスト11(3)の$proxyは値が読み出されることになります。結果としてProxyのFETCHが呼ばれ、sub { sqrt $_[0] }が実行されます。

左辺値として呼び出す

リスト11(5)ではlval_funcを左辺値として呼び出していますので、リスト11(3)の$proxyは値が代入されることになります。結果としてProxyのSTOREが呼ばれ、sub { $_[0] * $_[0] }が実行されます。リスト12(7)で見たように、この結果はリスト11(2)

tie my $proxy, 'Proxy', \$_[0],

で渡した$_[0]に格納されます。この$_[0]は元は$xですので、つまるところ$xに結果が入ることになります。


以上、Acme::Void、Acme::LValueからかなり強引にlvalueとtie変数を学ぶことができました! 苦しかったですね……。なお、lvalueサブルーチンは実験的な機能で将来のPerlのバージョンでは実装が変わる可能性があることにご注意ください。現実問題として、lvalue機能が実際に必要な機会はそうないと思います。詳細はperldoc perlsubでご確認ください。

まとめ

Acmeモジュールに魅了された人たちによって、さまざまなところでAcmeモジュールが紹介されています。一例を挙げるとPerl Advent Calendar Japanには過去2回Acme専用のトラックが開催されています[4]。また筆者にAcmeの由来をご教示くださった冨田さんによる紹介などなど……挙げていくとキリがありません。

仕事や勉強の合間にさまざまなAcmeモジュールと戯れると、一服の清涼剤になると思います。また、Acmeモジュールのおもしろ動作に直面して「どのようなしくみだろう?」と興味を持つこともあるでしょう。そこでソースコードを見ることで、Perlに対する新たな発見があるかもしれません。初めてCPANにアップしたモジュールがAcmeモジュールだった人もいらっしゃることでしょう。

Acmeというカテゴリが作られたおかげで私たちは現在、モジュール名で揉めることもなく安心して ジョークモジュールをリリース&インストールして楽しむことができます。先人の深い叡智(えいち)と、AcmeモジュールをCPANにアップしてくれる作者の方々に深く感謝したいと思います。

さて、次回の執筆者は近藤嘉雪さんで、テーマは「リファレンス入門」です。

おすすめ記事

記事・ニュース一覧