2022/12/30追記: 本記事は「Perlのプロトタイプ宣言を利用してtry-catch-finally文を作る」といった記事です。コメントにある通りTry::Tinyがまさしくこれをちゃんとやっています。
もし「Perlでtry-catch-finallyを行いたい」という場合は次をお読みください。
追記ここまで
サブルーチンのプロトタイプを使えば、
@nums = map { $_ * 2; } @nums;
といった、変った構文を自分で作ることができます。
これを使ってJavaのtry-catch-finally文っぽいものを書いてみました。
catch判定がスマートではないので、もう少し改善したい。。。
Error.pmを参考にした部分もあります。
use strict; use warnings; # catch sub catch(&@){ my $sub = shift; my @subs= @_; my $this = sub { print "catch Begin\n"; $sub->(@_); print "catch End\n"; }; return @subs ? ($this, @subs) : ($this); } # 識別子 use constant FINALLY_BLOCK_TYPENAME => 'FinallyBlockType'; # finally sub finally(&) { my $sub = shift; return bless sub{ print "finally Begin\n"; $sub->(); print "finally End\n"; }, FINALLY_BLOCK_TYPENAME; } # 最後尾にfinallyがあるはずなので、それをチェックする sub _findFinally(\@){ my $proc = pop @{$_[0]}; # if(ref $proc eq FINALLY_BLOCK_TYPENAME){ return $proc; } push @{$_[0]}, $proc; return undef; } # try sub try(&@){ my $try = shift; my $finally = _findFinally(@_); print "try Begin\n"; eval{ $try->(); }; print "try End\n"; # try中に例外発生 if($@){ my $error = $@; foreach my $catch (@_){ eval{ $catch->($error); }; if($@){ next; } last; } } if($finally){ $finally->(); } return undef; } ### ### # try -> catch(Exception) failed -> catch(Died) success!! -> finally print "\nCase1: test(try-catch(Exception)-catch(Died)-catch(Hoge)-finally)\n"; try { print " try!!\n"; die; } catch { my $e = shift; print " Exception catch check...\n"; $e =~ /Exception/ || die; print " catch!!\n"; } catch { my $e = shift; print " Died catch check...\n"; $e =~ /Died/ || die; print " catch!!\n"; } catch { my $e = shift; print " Hoge catch check...\n"; $e =~ /Hoge/ || die; print " catch!!\n"; } finally { print " finally!!\n"; }; print "\nCase2: try-catch(Exception)\n"; try { print " try!!\n"; die 'Exception'; } catch { shift =~ /Exception/ || die; print " catch!!\n"; }; print "\nCase3: try-finally\n"; try { print " try!!\n"; die 'Exception'; } finally { print " finally!!\n"; };
Case1: test(try-catch(Exception)-catch(Died)-catch(Hoge)-finally) try Begin try!! try End catch Begin Exception catch check... catch Begin Died catch check... catch!! catch End finally Begin finally!! finally End Case2: try-catch(Exception) try Begin try!! try End catch Begin catch!! catch End Case3: try-finally try Begin try!! try End finally Begin finally!! finally End
解説的なもの
サブルーチンのプロトタイプについてはperldocが良いでしょう。
perlsub - Perl のサブルーチン - perldoc.jp
まず簡単に構文っぽく書くにはどうサブルーチンを定義すればよいかを確認しました。
use strict; use warnings; sub a(&){ } a {1;}; sub b(&@){ } b {} (1..3); sub c(&\@){ $_[0]->($_[1]); } my @list = (1,2,3); c { print shift @{$_[0]}; } @{[1,2,3]}; sub d(\@&){ $_[1]->($_[0]); } # d @list {}; # syntax error d @list, sub{ print shift @{$_[0]}; }; sub ff(&@){ print "ff arg: @_\n"; my $sub = shift; my $this = sub { $sub->(); }; return @_ ? ($this, @_) : ($this); } sub fff(&@){ shift->(); print "@_\n"; } fff {0} ff {1} ff {2} ff {3};
aは思ったとおりです。
bとcは構文は全く同じですが、動作としてはbはリストのコピーを渡すのに対し、
cはリストのリファレンスを渡す(push,pop)ので、リストに操作を行うと呼び出し元のリストにも影響が出ます。
dはb,cのようにmap {} @a;と書くところを map @a {};とかけないか試したものです。しかし、構文エラーでした。
最後のff,fffは今回のtry-catch-finallyの肝となる部分です。
このとき、ffプロトタイプには、1個のCODEと可変長配列を許容するよう(&@)と定義しています。
まず、式は右から評価されます。この場合、ff {3}です。
ffは関数ですから、実行結果を返します。
今回はCODEをラップした形にしていますが、単純にCODEを返していることと同等です。
そして戻り値は、その左の式に返ります。次に実行される式は、
"ff {2} sub{3}" となります。
普通に書くと"ff {2} {3}"は文法エラーで、"ff {2}, sub{3}"としなければならないのですが、なぜか、この場合は後者のように評価されます。
このようにどんどん増えていくので、可変長にしているわけです。
(なので、ff sub{0}, 1, '2', 3.14, \4;などとしても通ります。)
これを使ってtry-catch-finallyを書いていけます。
・異常な文法をエラーにしろとか、
・try内でtryして外側のtryにcatchさせるとか、
・例外条件の書き方がスマートではないとか、
とか、不満はありますが、もう飽きたので、とりあえずこれでいいです。