連載企画 第1回 Perl で DSL する方法
昨年の 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 の名前空間にメソッドを宣言し、その上でコールバックを呼ぶことによって、さまざまなマジカルな技を使うことができます。
ねむいので今日はここまで。