id:papix です. この記事は, Perl Advent Calendar 2019の2日目の記事です. 昨日は, id:karupanerura さんの「2019年の最先端のPerl開発ボイラープレート」でした.
今日は, 昨日のエントリでも触れられていたSyntax::Keyword::Tryについて, さっくりとアレコレ綴りたいと思います.
Perlと例外処理
Perlで例外処理をするにあたって, 例えばJavaのようなtry
やcatch
といった仕組みは言語仕様として提供されていません.
そのため, Perl Monger達はtry
/catch
のような機能を提供するモジュールを用意して対処してきました.
その中でも著名なものの1つは, Try::Tinyでしょう.
Try::Tinyを使えば, 例外(die
)が起こるコードについて, 次のように書くことができます.
use Try::Tiny; sub run { try { die "!!!"; } catch { warn "recovery"; }; } run();
try
で囲まれたコードで例外が発生した場合, Try::Tinyはそれをよしなにcatchしてくれて, catch
で囲まれたコードを実行してくれる訳です.
Try::Tinyの罠
さて, Try::Tinyには, 何だかんだで誰もが1度はハマる罠が存在します. それが次のコードです.
use Try::Tiny; sub run { try { # ... 例外が起こるかもしれない処理 ... } catch { warn "recovery"; return 1; }; # ... 何かしらの処理 ... return 2; } my $result = run(); print "$result\n";
このコードを実行したとき, print "$result\n";
は何と表示されるでしょうか? コードを書いた人の意図としては,
try
で囲まれたコードで例外が発生すれば,catch
で囲まれたコードが実行され, 結果としてrun
は1を返すtry
で囲まれたコードで例外が発生しなければ,# ... 何かしらの処理 ...
の部分にある何かしらのロジックが実行され, 結果としてrun
は2を返す
...と考えていそうです. しかしながら, 例えば # ... 例外が起こるかもしれない処理 ...
を, 実際に例外が起こるようなコード(例えばdie("!!!")
)にしたとしても, このコードの実行結果(= print "$result\n";
の出力)は, 2になります.
これは何故でしょうか? 結論から言えば, Try::TinyはPerlの既存の仕組み(プロトタイプなど)を利用して, 強引にtry
/catch
風の構文を再現しているから... ということになります. 実は, 先のコードは下記のコードと等価なのです.
try ( sub { # ... 例外が起こるかもしれない処理 ... }, catch ( sub { warn "recovery"; return 1; }, ), );
そのため, return 1
はTry::Tinyが提供するcatch
という関数の第一引数に渡るサブルーチンリファレンスの返り値となり, 最終的にこの値はTry::Tinyが提供するtry
という関数の返り値となるわけです.
また, Try::Tinyの場合, 最後のコードブロックに;
をつける必要があるというのも, よくある罠と言えるでしょう.
use Try::Tiny; sub run { try { # ... 例外が起こるかもしれない処理 ... } catch { warn "recovery"; return 1; } # ← ここに ; が必要!!! # ... 何かしらの処理 ... return 2; }
Syntax::Keyword::Try
さて, そういったTry::Tinyの問題を解決する, Syntax::Keyword::Tryというモジュールがあります.
このモジュールを使うと, Perlにおける例外処理を次のように実装することができます:
use Syntax::Keyword::Try; sub run { try { # ... 例外が起こるかもしれない処理 ... } catch { warn "recovery"; return 1; } # ... 何かしらの処理 ... return 2; } my $result = run(); print "$result\n";
末尾の;
が不要だったり, # ... 例外が起こるかもしれない処理 ...
で例外が起おきた時も, 関数run
は当初意図していた通り, 1を返します(当然, 例外が発生しなければ return 2
によって2を返す).
Syntax::Keyword::Tryには, Try::Tinyに潜む罠が存在しないのです.
「キーワードプラグイン」
さて, このモジュールは, Perlの"キーワードプラグイン"という仕組みを使って実装されています. キーワードプラグインはPerl 5.12(2010年リリース)から実装された機能で, Perlのパーサーにフックを設定し, 構文を拡張できるようになっています.
Syntax::Keyword::Try以外にも, キーワードプラグインを利用したモジュールは公開されていて, 例えばFunction::Parameters(関数の定義をより良い形で記述できる), Keyword::Boolean(true
/false
を提供する), Switch::Plain(switch
構文を提供する)などがあります.
id:charsbar さんが2016年に参加した, London Perl Workshop 2016で, Syntax::Keyword::Tryを開発した, Paul Evans氏の発表があったそうですが,
Perlをもっとよくしていくには、公開されている機能をギリギリまで使い倒して、できる人たちに「そんな無茶なやり方よりもっといい方法がある」と言わせるといい
...と仰っていたそうです. キーワードプラグインによって今のPerlを拡張し, 日々の開発に役立て, それを通して未来のPerlに貢献する... というのは, とてもおもしろそうですね.
次回予告
...というわけで, キーワードプラグインの実装までご紹介出来ればよかったのですが, ちょっと準備に時間が足りず... 今回はここまでとさせてください. 改めて, Perl Advent Calendar 2019の16日目にて, 「キーワードプラグインを使ったモジュールの実装について」をご紹介させてもらえればと思います!!!!!!
Perl Advent Calendar 2019, 明日の担当は, take_3さんです.