Masteries

技術的なことや仕事に関することを書いていきます.

Syntax::Keyword::TryとPerlのキーワードプラグイン (その1)

id:papix です. この記事は, Perl Advent Calendar 2019の2日目の記事です. 昨日は, id:karupanerura さんの「2019年の最先端のPerl開発ボイラープレート」でした.

qiita.com

今日は, 昨日のエントリでも触れられていたSyntax::Keyword::Tryについて, さっくりとアレコレ綴りたいと思います.

Perlと例外処理

Perlで例外処理をするにあたって, 例えばJavaのようなtrycatchといった仕組みは言語仕様として提供されていません. そのため, Perl Monger達はtry/catchのような機能を提供するモジュールを用意して対処してきました. その中でも著名なものの1つは, Try::Tinyでしょう.

metacpan.org

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というモジュールがあります.

metacpan.org

このモジュールを使うと, 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構文を提供する)などがあります.

metacpan.org

metacpan.org

metacpan.org

id:charsbar さんが2016年に参加した, London Perl Workshop 2016で, Syntax::Keyword::Tryを開発した, Paul Evans氏の発表があったそうですが,

Perlをもっとよくしていくには、公開されている機能をギリギリまで使い倒して、できる人たちに「そんな無茶なやり方よりもっといい方法がある」と言わせるといい

charsbar.hatenadiary.org

...と仰っていたそうです. キーワードプラグインによって今のPerlを拡張し, 日々の開発に役立て, それを通して未来のPerlに貢献する... というのは, とてもおもしろそうですね.

次回予告

...というわけで, キーワードプラグインの実装までご紹介出来ればよかったのですが, ちょっと準備に時間が足りず... 今回はここまでとさせてください. 改めて, Perl Advent Calendar 2019の16日目にて, 「キーワードプラグインを使ったモジュールの実装について」をご紹介させてもらえればと思います!!!!!!

Perl Advent Calendar 2019, 明日の担当は, take_3さんです.