Perl(正確にはPerl5.X系列まで)にはtry-catch構文は存在しない。しかし、evalやcoderefを引数にとることができるなどの特徴を用い、幾つかの疑似try-catch的テクニックは存在する。Error.pmとうモジュールも公開されており、こちらを活用するとOOP的なtry-catchを実現できる。
参考ページ:
eval{}コードブロック中でのdieは、コードを抜けた後、 $@ 変数で参照できる。これを利用して、以下のような疑似try-catch機能を利用できる。
eval {
# do-something
if ( exception-condition ) {
die "exception message";
}
};
if ($@) {
&someErrorHandler($@);
}
これが、perl.comにも載っているし、自分自身、恐らくどこかしかで目にしたのだろう、いつの間にかおぼえていた疑似コードである。(ひょっとしたらperl.comで目にした記憶が時間軸を前後したのかも知れない。)
これについてはわざわざコードピースを示すまでもない。
perl.comでも掲載されている、Perlでtry-catch, そしてOOPな例外処理機構を使用するのにお奨めのモジュール、それがError.pmらしい。perl.comに掲載されている記事やCPANのPODを元に、簡単なコードピースで実験をしてみる。
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use Error qw(:try);
use Switch;
my $d = shift || 0;
my $a = -1;
try {
switch($d) {
case -2 {
# Error::Simpleをそのままthrowしてみる。
throw Error::Simple("d = -2");
}
case -1 {
# 元になるErrorオブジェクトを直にthrowしてみる。
throw Error(-text => "d = -1");
}
case 0 {
# Error::Simpleをシンプルに継承したものをthrowしてみる。
throw Error::TestException("d = 0");
}
# 以下はErrorを元にした独自Error
case 1 {
throw Test1HogeException("d = 1");
}
case 2 {
throw Test2HogeException("d = 2");
}
else {
throw HogeException("d = else");
}
}
$a = $d;
} catch HogeException with {
my $e = shift;
print "======== Hoge Exception ========\n";
print Dumper($e), "\n";
} catch Error with {
my $e = shift;
print "e = $e\n";
print Dumper($e), "\n";
print "========warn======\n";
warn $e->text;
} finally {
print "\n======= last a = [$a]\n";
};
package BoheException;
use base qw(Error);
package HogeException;
use base qw(Error);
use overload ('""' => 'stringify');
sub new {
my $self = shift;
my $text = "".shift;
my @args = ();
local $Error::Depth = $Error::Depth + 1;
local $Error::Debug = 1;
$self->SUPER::new(-text => $text, @args);
}
package Test1HogeException;
use base qw(HogeException);
package Test2HogeException;
use base qw(HogeException);
package Error::TestException;
use base qw(Error::Simple);
引数に応じて何パターンか試してみる。
e = d = -2 at ./try03.pl line 15.
$VAR1 = bless( {
'-file' => './try03.pl',
'-text' => 'd = -2',
'-line' => 15,
'-package' => 'main'
}, 'Error::Simple' );
========warn======
d = -2 at ./try03.pl line 43.
======= last a = [-1]
e = d = -1
$VAR1 = bless( {
'-file' => './try03.pl',
'-text' => 'd = -1',
'-line' => 18,
'-package' => 'main'
}, 'Error' );
========warn======
d = -1 at ./try03.pl line 43.
======= last a = [-1]
e = d = 0 at ./try03.pl line 21.
$VAR1 = bless( {
'-file' => './try03.pl',
'-text' => 'd = 0',
'-line' => 21,
'-package' => 'main'
}, 'Error::TestException' );
========warn======
d = 0 at ./try03.pl line 43.
======= last a = [-1]
======== Hoge Exception ========
$VAR1 = bless( {
'-stacktrace' => 'd = 1 at ./try03.pl line 24
',
'-file' => './try03.pl',
'-text' => 'd = 1',
'-line' => 24,
'-package' => 'main'
}, 'Test1HogeException' );
======= last a = [-1]
======== Hoge Exception ========
$VAR1 = bless( {
'-stacktrace' => 'd = 2 at ./try03.pl line 27
',
'-file' => './try03.pl',
'-text' => 'd = 2',
'-line' => 27,
'-package' => 'main'
}, 'Test2HogeException' );
======= last a = [-1]
======== Hoge Exception ========
$VAR1 = bless( {
'-stacktrace' => 'd = else at ./try03.pl line 30
',
'-file' => './try03.pl',
'-text' => 'd = else',
'-line' => 30,
'-package' => 'main'
}, 'HogeException' );
======= last a = [-1]
細かい解説は見れば自明なので省略するが、とりあえず独自のErrorクラスを構築し、Javaなどと同じ使い心地で使用できるのを確認できた。
上記例はあくまでも教科書通りに、Errorオブジェクトをthrowしている。では実際に、try {} 中で die や warn が発生するとどうなるかを確認しておく。
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use Error qw(:try);
try {
warn "warning!!";
die "die!!";
} catch Error with {
my $e = shift;
print "e = $e\n";
print Dumper($e), "\n";
print "========warn======\n";
warn $e->text;
} finally {
print "\n======= last \n";
};
warning!! at ./try04.pl line 9.
e = die!! at ./try04.pl line 10.
$VAR1 = bless( {
'-file' => './try04.pl',
'-text' => 'die!!',
'-line' => '10',
'-package' => 'Error'
}, 'Error::Simple' );
========warn======
die!! at ./try04.pl line 16.
======= last
die の場合、Error::Simpleにラッピングされてthrowされていることを確認できた。
warn, die と同じ効果になると思われる。
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use Carp;
use Error qw(:try);
try {
carp "carp!!";
croak "croak!!";
} catch Error with {
my $e = shift;
print "e = $e\n";
print Dumper($e), "\n";
print "========warn======\n";
warn $e->text;
} finally {
print "\n======= last \n";
};
carp!! at /usr/lib/perl5/site_perl/5.8.5/Error.pm line 428
e = croak!! at /usr/lib/perl5/site_perl/5.8.5/Error.pm line 428.
$VAR1 = bless( {
'-file' => '/usr/lib/perl5/site_perl/5.8.5/Error.pm',
'-text' => 'croak!!',
'-line' => '428',
'-package' => 'Error'
}, 'Error::Simple' );
========warn======
croak!! at ./try04.pl line 19.
======= last
ほぼ予想通りである。Perl/codepiece/carp01では、(carp,croak)と(cluck,confess)系で違いが見られなかった、とあるが、ここでようやく明らかな出力の差異が認められた。上記コードピースのcarp, croakをそれぞれcluck, confessで置き換えた結果を下に示す。
cluck!! at ./try04.pl line 12
main::__ANON__() called at /usr/lib/perl5/site_perl/5.8.5/Error.pm line 428
eval {...} called at /usr/lib/perl5/site_perl/5.8.5/Error.pm line 420
Error::subs::try('CODE(0x816446c)', 'HASH(0x8164448)') called at ./try04.pl line 24
e = confess!! at ./try04.pl line 15
main::__ANON__() called at /usr/lib/perl5/site_perl/5.8.5/Error.pm line 428
eval {...} called at /usr/lib/perl5/site_perl/5.8.5/Error.pm line 420
Error::subs::try('CODE(0x816446c)', 'HASH(0x8164448)') called at ./try04.pl line 24.
$VAR1 = bless( {
'-file' => './try04.pl',
'-text' => 'confess!! at ./try04.pl line 15
main::__ANON__() called at /usr/lib/perl5/site_perl/5.8.5/Error.pm line 428
eval {...} called at /usr/lib/perl5/site_perl/5.8.5/Error.pm line 420
Error::subs::try(\'CODE(0x816446c)\', \'HASH(0x8164448)\') called',
'-line' => '24',
'-package' => 'Error'
}, 'Error::Simple' );
========warn======
confess!! at ./try04.pl line 15
main::__ANON__() called at /usr/lib/perl5/site_perl/5.8.5/Error.pm line 428
eval {...} called at /usr/lib/perl5/site_perl/5.8.5/Error.pm line 420
Error::subs::try('CODE(0x816446c)', 'HASH(0x8164448)') called at ./try04.pl line 21.
======= last
このように、cluck, confessを使用すると、細かいスタックトレースが出力できることが分かった。
結論として Error.pm と Carp 系は共存可能 であることを確認できた。