Entry

Perl の勉強メモ - 「関数テンプレートとしてのクロージャ」の巻

2006年04月07日

数日前から,Perl の勉強がてら Movable Type のソースを眺めていて,MT::Object::Entry::load() がどうやって実装されているのか調べていたんですけれど,lib/MT 以下を「grep "^sub load" *.pm」しても,それらしき定義がないもんで混乱していました。Object::Blog::load() は定義されてるんですけどね。どういうことなんざましょ……。

というわけで,ここら辺がどうなっているのか,ちょっと調べてみました。

MT ライブラリの perldoc によると,データの元締めは MT::Object とのことなので,Object.pm を読んでみます。結果,_mk_passthru() なるメソッドを発見。具体的にデータをやりとりしているのは,以下の箇所のようです。

my @rc = $DRIVER->$method(@_);

_mk_passthru() の名前(make pass through)通り,直接 ObjectDriver のインスタンス・メソッドを呼び出していると……($DRIVER は ObjectDriver のインスタンス)。結局,load() の本体は ObjectDriver クラスの方にあったんですね。ともあれ,_mk_passthru() の使い方については書き留めておかないと忘れそうなので,メモしておきます。いわゆる,「クロージャを関数テンプレートとして使う」というアレです。

Object.pm で load() がどう書かれているのかというと,次のようになっています。

sub _mk_passthru {
    my($method) = @_;
    sub {
        my($this) = $_[0];
        # snip
        my @rc = $DRIVER->$method(@_);
        # snip
    }
}

{
    no strict 'refs';
    *load = _mk_passthru('load');
    *load_iter = _mk_passthru('load_iter');
    *save = _mk_passthru('save');
    *remove = _mk_passthru('remove');
    *remove_all = _mk_passthru('remove_all');
    *exists = _mk_passthru('exists');
    *count = _mk_passthru('count');
    *count_group_by = _mk_passthru('count_group_by');
}

型グロブを使って mk_passthru() 内の無名サブルーチンに名前を付けています。やっと具体的に load や load_iter といった文字の並びが出てきました。

もっとも,ここでは「型グロブを使って……云々」なんて,さも知った風に書いているけれども,あたしゃ当初この意味がさっぱり分からなくて難儀してしまいました(←Perl の知識もままならない人)。どんなことをやっているのか順を追って見ることにすると,こんな感じで理解しておけばよさそうです。多分……恐らく……。

  1. _mk_passthru() の呼出し(例えば,*load = _mk_passthru('load');)で,$method をセット。
  2. _mk_passthru() の返値は sub {} 以下で定義されている無名サブルーチン(へのリファレンス)。
  3. 返値を型グロブで受ける(例えば,*load)。これで名前が付く。
  4. $method は my で宣言されているけれど,型グロブが指している sub{} 内で使われているから消えない。
  5. これで,型グロブで名前がついた無名サブルーチン(ややこしいな)は,外側から見る限り普通のサブルーチンと同じように振舞える。

この my な宣言にもかかわらず $method が残る,というのがミソで,クロージャと呼ばれているんだそうです。そういえば,その昔,Perl で挫折したのは型グロブとクロージャでした……鬼門だったんですね。もっとも,この方法自体は,聖典ラクダ本を引いたところ,そのままの形で紹介されていました。

クロージャを関数テンプレートとして使うことにより、似たような動作をする多数の関数を生成することができる。HTML のフォントをさまざまな色に変更する一連の関数が必要だったとしよう:

print "Be ", red("careful"), "with that ", green("light"), "!!!";

red 関数と green 関数は、とてもよく似たものになるだろう。関数には名前を付けたいところだが、クロージャは無名サブルーチンにすぎないので名前を持っていない。これを解決するために、無名サブルーチンに名前を付けるというトリックを利用しよう。無名サブルーチンへのリファレンスを使いたい名前の型グロブに代入することによって、無名サブルーチンにその名前を与えることができる。(「10.1 シンボルテーブル」を参照のこと。)われわれのケースでは、2つの名前ーー大文字の名前と小文字の名前ーーを付けることにする:

@colors = qw(red blue green yellow orange purple violet);
for my $name (@colors) {
    no strict 'refs';        # シンボリックリファレンスを許可する
    *$name = *{uc $name} = sub { "<FONT COLOR='$name'>@_</FONT>" };
}

これにより、red、RED、blue、BLUE といった名前の関数を呼び出せば、適切なクロージャが起動されるようになる。

『プログラミングPerl 第3版 VOLUME1』(Larry Wall 他,オライリー・ジャパン,2002年,p302)

ただ物知らずなだけだったと……あははは。

それにしても,今回,クロージャを使った関数テンプレートなるテクは勉強になったものの,本題である MT::Object::Entry::load() がどうやって実装されているのか,については,まったく解決していなかったのでした。メソッド探索は始まったばかり……トホホ。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN