Blog

連載企画 第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 の名前空間にメソッドを宣言し、その上でコールバックを呼ぶことによって、さまざまなマジカルな技を使うことができます。


ねむいので今日はここまで。