久しぶりに会社で 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
を利用するとヒントが得られるかなと。