モダンPerlの世界へようこそ

第10回 Class::Meta::Express:もっと読みやすく,周囲への影響は最小限に

この記事を読むのに必要な時間:およそ 4 分

Makefile.PLの中で使う分には問題ありませんが

もっとも,Module::Installの方式にも弱点はあります。

たとえば,Makefile.PLという,ふつうは1回しか実行されないスクリプトのなかではそれほど問題にはならないことですが,Module::Installは大量のシュガー関数をエクスポートしているので,インポート先のクラスでうっかり同名の関数/メソッドを定義してしまうと,期待した動作が得られないことがあります。

ことModule::Installについては,どのクラスでどのシュガー関数が定義されているかわかりづらい,という問題もあります。Module::Installは拡張性を重視してプラガブルな構成になっているのですが,シュガー関数の名前とクラスの名前はかならずしも一致しないので,慣れないと個々のシュガー関数のPODを見つけるのにも苦労します(これはModule::Installに限らず,継承関係が複雑なモジュールにはつきものの問題ですが,エクスポートされてくるシュガー関数は一見Perlの組み込み関数のようにも見えてしまうため,なおさら出自がわかりづらくなっています⁠⁠。

また,ここで宣言した設定は,内部的には$Module::Install::MAINというパッケージ変数に保存されているオブジェクトに格納されます。Module::Installの用途的にはこれで十分用事は足りていますが,Mooseのようにクラスを定義するようなモジュールで同じ手法を利用する場合は保存先を考え直す必要があるでしょう。

noによるキーワードの除去

そのMooseですが,これも大量のシュガー関数を使いますので,基本的にはModule::Installと同じ問題を抱えています。

ただし,Mooseの場合,自分がエクスポートしたシュガー関数については,no Mooseを実行すれば取り除くことができるようになっています。

これまではスペースの都合で省略してきましたが,一般的にMooseを使ったクラス定義では,それ以上Mooseのキーワードが必要なくなった時点で,下の例のようにno Moose;という行を入れておくのがベストプラクティス。こうしておくと,たとえばこのような行儀の悪いコードを書いても正しく動作するようになります。

package MyClass;

use Moose;

has 'foo' => (is => 'rw', isa => 'Str');

no Moose;

sub has { shift; print @_, "\n" }

package main;

my $obj = MyClass->new;
$obj->has('foo');  # foo
$obj->foo('bar');
print $obj->foo;   # bar

1;

範囲を明示的に指定してよいとこ取りをする

もっとも,この問題ではデイヴィッド・ホイーラー(David Wheeler)氏が2006年5月にリリースしたClass::Meta::Expressなどのほうが一歩先を進んでいます。

このClass::Meta::Expressは2004年にリリースされたClass::Metaという,Class::MOPの先輩格にあたるイントロスペクション用モジュールのラッパーなのですが,これを使うと,もともとはこのように冗長な書き方をしていたClass::Metaの定義が,

package Person;

use strict;
use warnings;
use Class::Meta;
use Class::Meta::Types::String;

BEGIN {
    my $meta = Class::Meta->new();

    $meta->add_constructor( name => 'new' );

    $meta->add_attribute(
        name     => 'name',
        is       => 'string',
        required => 1,
    );

    $meta->add_method(
        name => 'say_hello',
        code => sub {
            my $self = shift;
            print "Hello, I'm ", $self->name, "\n";
        },
    );

    $meta->build;
}

このように簡潔に書けるようになります。

package Person;

use strict;
use warnings;
use Class::Meta::Express;

BEGIN {
    class {
        ctor 'new';

        has name => (is => 'string', required => 1);

        method say_hello => sub {
            my $self = shift;
            print "Hello, I'm ", $self->name, "\n";
        };
    };
}

これだけならMooseと大差ないように見えますが(実際,この書き方はMooseをまねたものであることがPODに明記されています⁠⁠,Class::Meta::Expressの場合,宣言部をclassというブロック(に見える無名関数)内に押し込めることによって,クラスの定義が済んだら利用したシュガー関数を自動的に取り除いてくれる,というのが大きなポイント。

また,$meta->buildのようなお決まりの終了処理も(ExtUtils::MakeMakerのWriteMakefileのように)すべてclassのほうで処理してくれるので,よりタイプ数の少ない宣言が可能になっています。

また,当初はMooseやModule::Installのように宣言をベタ書きしていたものの,2007年初頭に後方互換性を捨ててまでこの書き方を採用したJifty::DBIでは,この宣言をuseと組み合わせることによって,明示的なBEGINブロックを不要にしています。

package MyApp::Table;

use strict;
use warnings;
use Jifty::DBI::Schema;
use Jifty::DBI::Record schema {
    column foo => type is 'text';
};

やりすぎにはご用心

このように,モダンPerlの世界では,シュガー関数ひとつとっても昔より気を遣った書き方がされるようになっているのですが,なにごとにも限度というものはあります。

たとえば,フロリアン・ラグヴィッツ(Florian Ragwitz)氏が2008年10月にリリースしたMooseX::Declareというモジュールを使うと,このような書き方さえできるようになるのですが,

use MooseX::Declare;

class BankAccount {
    has 'balance' => ( isa => 'Num', is => 'rw', default => 0 );

    method deposit (Num $amount) {
        $self->balance( $self->balance + $amount );
    }

    method withdraw (Num $amount) {
        my $current_balance = $self->balance();
        ( $current_balance >= $amount )
            || confess "Account overdrawn";
        $self->balance( $current_balance - $amount );
    }
}

これ,package BankAccount;のような定番のパッケージ宣言すら省略してしまうため,そのままではPAUSEやCPAN検索サイトなどにパッケージが存在しないものと誤解されてしまう,という問題が知られています(そのため,CPANにアップされているモジュールではわざわざ別にpackageを使ったパッケージ宣言が書かれています⁠⁠。

MooseX::Declareの場合,裏ではソースフィルタ的な処理も行われているので,ここで取り上げたシュガー関数と同列に扱うことはできませんが,このように目にも手にもやさしい書き方を追究していく過程では,小粒でもぴりりと辛いツールがいくつも書かれています。次回はそのような小物に少しスポットを当ててみましょう。

著者プロフィール

石垣憲一(いしがきけんいち)

あるときは翻訳家。あるときはPerlプログラマ。先日『カクテルホントのうんちく話』(柴田書店)を上梓。最新刊は『ガリア戦記』(平凡社ライブラリー)。

URLhttp://d.hatena.ne.jp/charsbar/