久しぶりに会社で 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の中のbarをbazに置換せよ - ただし、元ファイルは
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 では try や catch を文法的にサポートしているわけではありません。
これはどのようにして実現されているのか。こちらも 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 を利用するとヒントが得られるかなと。