昨年の YAPC の頃に書こうと思ってたネタを id:cho45 さんのひとことで不意に思いだしたのでここに記す。
まず、DSL とはなにか。この連載企画では↓こういうのを DSL とよぶことにします。
session 'testuser' => run {
flow 'log in and out' => check {
flow 'log in' => check {
get 'http://localhost/';
fill form 'login' => {
username => 'testuser',
password => 'drowssap',
};
content should contain 'log out';
};
flow 'log out' => check {
get 'http://localhost/';
click href 'log out';
};
};
};
さて、こういう変態的なコードをどう書くか。それが主題ですね。
まずは基本の run { } です。
run {
say 'ya'
};
なんで、素直に sub つかわないのか。それはサブはカッコワルイからです。それだけです。それ以外に理由はありません。
この run { } の { } はハッシュではなく anonymous function です。
そのままだと、これが anonymous function だということは Perl ちゃんには理解できません。そのままコンパイルすると hash として評価されてしまいます。
なので、あらかじめ「run の後には function がくるよー」。という宣言をしておきます。これはプロトタイプ宣言によって行うことができます。
sub run(&) { $_[0]->() }
run {
say 'ya'
};
これで OK.かっこよくなりました。
2つのコードリファレンスを引数にとりたい!というようなケースを考えます。
sub foo(&&) { $_[0]->(); $_[1]->() }
foo { say "Hey!" }, { say "Ho!" } # => "Hey!\nHo!"
というコードはコンパイルエラーです。
sub foo(&&) { $_[0]->(); $_[1]->() }
foo sub { say "Hey!" }, sub { say "Ho!" } # => "Hey!\nHo!"
なら大丈夫だけど、カッコワルイ。
そんなときは、ラッパー的な関数を噛ませましょう。
sub foo($$) { $_[0]->(); $_[1]->() }
sub bar(&) { $_[0] };
sub baz(&) { $_[0] };
foo bar { say "Hey!" }, baz { say "Ho!" }; # => "Hey!\nHo!"
ほーら。簡単でしょう。
さて、このコードリファレンスを取得する仕組みがどういう風にやくだつか、という話題。
sub run(&) {
my $pkg = caller;
no strict 'refs';
no warnings 'redefine';
local *{"$pkg\::oh"} = sub { say "Oh! my Konbu!" };
$_[0]->()
}
sub oh { say "Oh! sadaharu!" }
run {
oh();
};
oh();
というコードの実行結果は
Oh! my Konbu!
Oh! sadaharu!
となります。
sub run(&) {
my $pkg = caller;
no strict 'refs';
local *{"$pkg\::oh"} = sub { say "Oh! my Konbu!" };
$_[0]->();
}
run {
oh();
};
だと
Oh! my Konbu!
と出力されます。
local で caller の名前空間にメソッドを宣言し、その上でコールバックを呼ぶことによって、さまざまなマジカルな技を使うことができます。
ねむいので今日はここまで。