Plack::Builder から見る内部DSLを作るテクニック
それでは、
PSGIは、
Plack::Builderの使い方
まずPlack::Builderの使い方を見てみます。
use Plack::Builder;
my $app1 = sub { ... };
my $app2 = sub { ... };
builder {
enable 'ReverseProxy';
enable 'Runtime';
mount '/foo' => $app1;
mount '/' => $app2;
};
builder
およびその内部がPSGIアプリケーションを定義するDSLになっています。enable
でPlack::Middlewareを有効にし、mount
でURLをマップしています。あまりPSGIに馴染みがない人でも、
Plack::Builderのコード
このDSLを実現しているPlack::Builderのコードがリスト1です。 リスト1は紙幅の都合で一部のみであり、 ※誌面の都合上、 では、 リスト1 プロトタイプとはサブルーチンの取り得る引数の型のことで、 もしプロトタイプ宣言なしにこう書いてしまうと、 このようにDSL用途では、 ところで さて次はリスト1 ここで先のMojoliciousの例を振り返ってみます。オブジェクト指向版では、 この例からわかるように、 続いてはリスト1 身近な例としては、 リスト1 こうして さてPlack::Builderのコードには出てきませんでしたが、 シンボルテーブルとは、 ここで と定義するのであれば ただし 以上、 さて、$ perldoc -m Plack::Builder
our $_add = our $_add_if = our $_mount = sub { ―(1)
Carp::croak("enable/mount should be called "
. "inside builder {} block");
};
sub enable { $_add->(@_) }
sub enable_if(&$@) { $_add_if->(@_) }
sub mount {
my $self = shift;
if (Scalar::Util::blessed($self)) {
$self->_mount(@_);
}else{
$_mount->($self, @_);
}
}
sub builder(&) { ―(2)
my $block = shift;
my $self = __PACKAGE__->new; ―(3)
my $mount_is_called;
my $urlmap = Plack::App::URLMap->new;
local $_mount = sub {
$mount_is_called++;
$urlmap->map(@_);
$urlmap;
};
local $_add = sub { ―(4)
$self->add_middleware(@_);
};
local $_add_if = sub {
$self->add_middleware_if(@_);
};
my $app = $block->(); ―(5)
if ($mount_is_called) {
if ($app ne $urlmap) {
Carp::carp("WARNING: You used mount() ...");
} else {
$app = $app->to_app;
}
}
$app = $app->to_app
if $app
and Scalar::Util::blessed($app)
and $app->can('to_app');
$self->to_app($app);
}
内部DSLを作るうえでのテクニック
builder
サブルーチンを読み解きながら、プロトタイプ──構文解析のヒントを与える
builder
サブルーチンは後ろに(&)
を引き連れて定義されています。これはプロトタイプ宣言です。sub builder(&)
によってbuilder
サブルーチンはサブルーチンリファレンスを引数に取る」sub
なしですっきりと書くことができます。builder {
...;
};
{}
を無名ハッシュと解釈してしまいシンタックスエラーとなります。コンテキストが重要
$self
にPlack::Builderインスタンスを設定しています。すなわち$routes
にルーティングを$routes->get('/')...
のように定義していきました。一方、get '/' =>...
と定義していきました。しかし、builder
サブルーチンにおいては、local──あるスコープだけ意味を変える
local
です。local
を使うと、$/
をundef
にすることがあります。my $content = do {
open my $fh, "<", $file or die "$file: $!";
local $/ = undef;
<$fh>;
};
$_add
はもともと例外を出すだけのサブルーチンリファレンスとして定義されていました。それが主題$self
が設定されたbuilder
サブルーチン内でだけは、local $_add = sub {$self->add_
とMiddlewareを適用する役割を与えられるのです。builder
サブルーチン内では主題$self
が設定され、$_add
の定義が変更されました。そして満を持してリスト1$block
が実行され、シンボルテーブル ── 動的にシンボルを定義する
*Module::foo
でModuleパッケージのfoo識別子のシンボルテーブルにアクセスできます。たとえば、use strict;
{
no strict 'refs';
my $package = "YourApp";
*{$package . "::foo"} = sub { print "foo!" };
}
YourApp::foo(); # foo!
no strict 'refs'
としている理由について説明します。もし$package
という変数を使わず、*YourApp::foo = sub { print "foo!" };
no strict 'refs'
は必要ありません。しかしDSLでの用途を考えれば、no strict 'refs'
を指定する必要があります。no strict 'refs'
を使う場合は、no strict 'refs'
を宣言するようにしてください。まとめ