Perl の例外処理

Catalyst で action を遷移する場合は,コンテキストオブジェクトの forward() メソッドを使うのが一般的だと思いますが,同じような役割のメソッドとして detach() というのもあります。違いは,forward() は遷移先から return 等で戻ってくるのに対し,detach() は遷移しっぱなしです。

sub act1 : Private {
  my ($self, $c) = @_;
  $c->forward('act9');
  # ファイナライズとかやります?
}

sub act2 : Private {
  my ($self, $c) = @_;
  $c->detach('act9');
  # ここにはもうきません
}

エラー時やリダイレクト時に,遷移元の記述がスマートになりますね。
この「サブルーチンとして呼び出したのに戻ってこない」ってどのように実装しているのだろう?と思って調べたら,Catalyst::Dispatcher にありました。

sub detach {
    my ( $self, $c, $command, @args ) = @_;
    $c->forward( $command, @args ) if $command;
    die $Catalyst::DETACH;
}

実質 forward で処理するのですが最後に die する,と。die しちゃまずいんじゃないかと一瞬思いますが,eval 句で評価(実行)中の場合,die してもプログラム自体はアボートしないんですね(※)。で,Catalyst の場合,Catalyst::execute() の中で(それ以外でもばしばし使ってますが)アクションを eval しているので,最終的にはそこで捕捉できるので問題ないんです。

さて,※でさらっと書きましたが,この eval 〜 die のセットは,Perl での例外処理等によく用います。詳しくは,Perl のマニュアルの eval の項die の項をご覧下さい。私もあらためて読んで,「eval {〜};」の文法だと「〜」の部分はコンパイルフェーズでコンパイルしてしまうんだ!という新たな発見がありました*1


さて,Perl での例外処理ですが,他の OO 言語のように「try 〜 catch」スタイルで目に易しい書き方がしたい,という向きには,Error モジュール日本語訳)や Exceptions モジュールがあります。後者についてはあまり言及されているページがないですし*2,リリースも多くないので,前者が de-facto なんですかね。
既存のフレームワークだと(整合性をとるために)自前の例外処理機構を用意している物も多いですが,そういったものに頼らないスタンドアロンなプログラムでは利用してみるのも一興ではないでしょうか。

追記

Exception::Class モジュールというものがあることに気づきませんでした。

  • Error モジュールは try 〜 catch 〜 finally という他の言語の例外処理機構に近い syntax で例外を扱えます。
    • 例外クラスのベースとなる物は Error クラスです。
    • 他に Error::Simple という例外クラスも添付されていますし,独自の例外クラスを定義することができます。
  • Exception::Class モジュールは例外クラスのヒエラルキーをツリー状に構成できます。
    • 例外処理の syntax は,YourExceptionClass->throw で throw し,Exception::Class->caught で catch するという,他の言語との類似度が低いものです。
    • でもこれはこれで Perl 的というか OO 的で,そんなにひどくない気もします。
    • try 〜 catch な syntax suger は,Exception::Class::TryCatch で得ることができます。
    • 実は Error モジュールと組み合わせて try 〜 catch syntax を実現することもできます。Exception::Class の perldoc に記載されています。
  • Error モジュールは,try 節の実現にコードリファレンス(無名 sub のことかな?)を使っているので変にネストしたりすると古い Perl では memory leak する可能性があるらしいです。E::C::TryCatch ではそれを使っていないので robust らしいです。

両者とも自分で使ったことがないので推測しか書けません。ごめんなさい。ちなみにいずれも開発はそれなりに活発です。

*1:今まで eval は常に実行フェーズでコンパイルすると思いこんでいたんで mod_perl のメリットが失われる気がしていたんですが,そんなことないんですね

*2:$SIG{__DIE__} を上書きするのが不人気の理由かも