理系学生日記

おまえはいつまで学生気分なのか

B::Deparse で One-Liner を理解する

久しぶりに会社で Perl の話をしました。何の話かというと B::Deparseです。

最近 Perl を使うのは One-Liner くらいなのですが、一般に One-Liner は可読性が犠牲になります。 一体この One-Liner は何をしているのか、というのを説明するときには B::Deparse を使うことで説明が容易になります。

例えば、以下のような Perl の One-Liner があるとします。

$ perl -pi'.orig' -e 's/bar/baz/' fileA

この One-Liner の意味するところは以下になります。

  • ファイル fileA の中の barbaz に置換せよ
  • ただし、元ファイルは fileA.orig としてバックアップしておけ

これをわかりやすくするのが B::Deparse です。まずは具体的に使ってみましょう。

$ perl -MO=Deparse -pi'.orig' -e 's/bar/baz/' fileA
BEGIN { $^I = ".orig"; }
LINE: while (defined($_ = <ARGV>)) {
    s/bar/baz/;
}
continue {
    die "-p destination: $!\n" unless print $_;
}
-e syntax OK

ずっとわかりやすくなりました。B::Deparse は、Perl のコンパイラが生成した中間コードを解析した上でソースコードを復元してくれます。 これを利用すれば、One-Liner が具体的にどのような処理をしているのかをコードとして理解できます。 ($^I$! がわかんないという場合は、perldoc perlvar をご参照ください)

ほかにも、例えば Try::Tiny を使えば仮想的に Perl で Try-Catch-Finally を実現できます。

# handle errors with a catch handler
try {
  die "foo";
} catch {
  warn "caught error: $_"; # not $@
};

一方で、Perl では trycatch を文法的にサポートしているわけではありません。 これはどのようにして実現されているのか。こちらも B::Deparse を利用して確認してみましょう。

$ perl -MO=Deparse -MTry::Tiny -e 'try { die "foo"; } catch { warn "caught error: $_"; }'
use Try::Tiny;
try sub {
    die 'foo';
}
, catch(sub {
    warn "caught error: $_";
}
);
-e syntax OK

try のあとに sub があり、無名関数として宣言されていることがわかります。さらに、その無名関数のあとには , があり、catch のあとにまた無名関数が現れています。 これは、以下のような構造になっていることを意味しています。

try (sub {...}, catch(sub {...}))
  • try 関数の引数として無名関数 1 つと、catch 関数の呼び出し結果が渡されている
  • catch 関数には、無名関数が渡されている

もちろん、実体を理解するには Perl における Prototypeの知識が必要ではありますが。

One-Liner を含め、いったいこのコードは何をしているんだろう、という疑問があるときは B::Deparse を利用するとヒントが得られるかなと。