引数の名前を知る方法 Data::Dumper::Names

みんなの好きなprint 文デバッグに、ちょっとした悩みがあるとしたら、冗長で"こダサい" ということかもしれません。
こんな風に書いたことがあるでしょう?

print '$target: ' . "$target\n";

これを簡単にするために、専用のサブルーチンを用意してみます。

# デバッグプリント
sub h(@){
    map {print $_ . "\n"} @_; 
}

# 使ってみる
$target = 'test';
h $target;

<実行結果>
test

しかし、最初のprint 文で書いた例を再現しようとすると、依然として

h '$target: ' . "$target";

と書かなくてはいけません。

h $target;

だけで、

$target: 'test'

のように表示できるようにすることはできないのでしょうか。

そして、せっかくですから次に挙げるものがどのように表示できるかも確認してみたいと思います。

# 見たいものたち
123;                    # 即値
'abc';                  # 即値
[1,2];                  # 無名配列
{a=>1,b=>2};            # 無名ハッシュ
my  $s   = '123';       # スカラー
my  @a   = qw/1 2 3 4/; # 配列
my  %h   = qw/A 1 B 2/; # ハッシュ
our $o   = 'our';       # our 変数
local $l = 'local';     # local 変数
$g       = 'global';    # グローバル変数
sub {};                 # 無名サブルーチン
sub func {'x'};         # サブルーチン

最初に結論を書いてしまうと、さすがに上に挙げたものすべてを適切に表示するのは難しいということでした。

  • my 変数と、our 変数以外は名前は取れない
  • my 変数と、our 変数とでは名前の取得方法が異なるので、統一的に扱うことが難しい
  • (これは当たり前だが)即値、無名系は名前が取れない

しかしながら、実用上差し支えないと思われる程度には可能でした。

その扉を開くのに、yokohama.pm のみなさんのお力をお借りしました。
irc(irc.freenode.net の#yokohama.pm) で質問をさせていただいた次第です。

Devel::ArgNames

まずZIGOROu さんからは、Devel::ArgNames(http://search.cpan.org/dist/Devel-ArgNames/lib/Devel/ArgNames.pm)でした。

18:42:51 zigorou: っぽぃのが出たので
18:42:58 zigorou: 内容は良く知りません><
18:54:25 nekokak: use Devel::ArgNames;
18:54:26 nekokak: sub hoge {
18:54:26 nekokak: warn 'hoge() called with arguments:'
18:54:26 nekokak: . join(", ", map { defined() ? $_ : "" } arg_names(@_) );
18:54:26 nekokak: }
18:54:26 nekokak: my $moge = 'moge';
18:54:28 nekokak: &hoge($moge, 'uhe');
18:54:30 nekokak: で
18:54:32 nekokak: できますね
18:54:35 nekokak: >Devel::ArgNames
18:54:50 nekokak: hoge() called with arguments:$moge,
18:54:51 nekokak: とでますた

なるほど、いけそうですね。
自分でもやってみる。

use Devel::ArgNames;

my  $s   = '123';       # スカラー
my  @a   = qw/1 2 3 4/; # 配列
my  %h   = qw/A 1 B 2/; # ハッシュ
our $o   = 'our';       # our 変数
local $l = 'local';     # local 変数
$g       = 'global';    # グローバル変数
sub {};                 # 無名サブルーチン
sub func {'x'};         # サブルーチン

sub test1 {
    warn 'test1() called with arguments:'
       . join(", ", map { defined() ? $_ : "<unknown>" } arg_names() );
}

test1(123,'abc',[1,2],{a=>1,b=>2},$s,\$s,\@a,\%h,$o,\$o,$l,\$l,$g,\$g,sub{},&func,\&func);
}

<実行結果>
test1() called with arguments:<unknown>, <unknown>, <unknown>, <unknown>, $s,
<unknown>, <unknown>, <unknown>, $o, <unknown>, <unknown>, <unknown>,
<unknown>, <unknown>, <unknown>, <unknown>, <unknown> at test.pl line 14.

Devel::ArgNames は、確かに$s や$o の名前が取れているものの、(実用性としてはこれでもいいと思う)
その他ではちょっと寂しい感じになりました。

PadWalker(http://fleur.hio.jp/perldoc/mix/lib/PadWalker.mix.html)

18:46:38 kazeburo____: Padwalker?
18:46:45 nekokak: PadWalker ですね
18:52:36 bonnu: $args{var_name(1, \$_)} = $_ for @_ とかで一覧になりそうですね。PadWalker

PadWalker も、ドキュメントを読みながら試してみたのがこちら。
なお、なるべく親切な表示ができないかなと表示仕分けを試みてみています。

#!/usr/bin/perl
use PadWalker qw(peek_my peek_our peek_sub closed_over var_name);

my  $s   = '123';       # スカラー
my  @a   = qw/1 2 3 4/; # 配列
my  %h   = qw/A 1 B 2/; # ハッシュ
our $o   = 'our';       # our 変数
local $l = 'local';     # local 変数
$g       = 'global';    # グローバル変数
sub {};                 # 無名サブルーチン
sub func {'x'};         # サブルーチン

sub test2{
    for(@_){
        if(ref $_ eq 'SCALAR'){
            print var_name(1, $_);
        }
        elsif(ref $_ eq 'ARRAY'){
            print var_name(1, $_) || "無名配列";
        }
        elsif(ref $_ eq 'HASH'){
            print var_name(1, $_) || "無名ハッシュ";
        }
        elsif(ref $_ eq 'CODE'){
            # 無名サブルーチンの場合はなぜか& が返ってくる
            print ((var_name(1, $_) eq '&') ? '無名サブルーチン' : "サブルーチン");
        }
        else{
            print "即値";
        }
        print ":$_\n";
    }
}

test2(123,'abc',[1,2],{a=>1,b=>2},$s,\$s,\@a,\%h,$o,\$o,$l,\$l,$g,\$g,sub{},&func,\&func);

<実行結果>
即値:123
即値:abc
無名配列:ARRAY(0x86fb66c)
無名ハッシュ:HASH(0x86fb69c)
即値:123                        # ここの変数名は取れてもいいのにな
$s:SCALAR(0x868b72c)            # 参照渡しだと名前が取れている
@a:ARRAY(0x868b75c)
%h:HASH(0x868b78c)
即値:our                        # ここの変数名は取れてもいいのにな
:SCALAR(0x86fb2d0)              # our 変数はvar_name では取れないようだ
即値:local                      # local, またglobal 変数の名前の取り方は無かった
:SCALAR(0x86fb660)              # 〃
即値:global                     # 〃
:SCALAR(0x86fb318)              # 〃
無名サブルーチン:CODE(0x86fb5e8)
即値:x
サブルーチン:CODE(0x86fb39c)    # サブルーチンについても名前を取る事ができなかった

ここで使ったvar_name ではour 変数の名前がうまく取得できないようです。
our 変数の名前を取るのには専用のpeek_our があったりしますので、それを工夫して作り込めばなんとかわかるというところでしょうか。
また、local 変数、global 変数はそもそも取得できないようです。そのような機能がありませんでした。

Data::Dumper::Names

後日ZIGOROu さんがmiyagawa さんに教わったといって教えてくれたのがこれ。

use Data::Dumper::Names;

my  $s   = '123';       # スカラー
my  @a   = qw/1 2 3 4/; # 配列
my  %h   = qw/A 1 B 2/; # ハッシュ
our $o   = 'our';       # our 変数
local $l = 'local';     # local 変数
$g       = 'global';    # グローバル変数
sub {};                 # 無名サブルーチン
sub func {'x'};         # サブルーチン

sub test3{
    print Dumper @_;
}

test3(123,'abc',$s,\$s,$our,\$our,$global,\$global,\@a,[1,2],\%h,{a=>1,b=>2},sub{},\&func,&func);

<実行結果>
$VAR1 = 123;
$VAR2 = 'abc';
$VAR3 = [
          1,
          2
        ];
$VAR4 = {
          'a' => 1,
          'b' => 2
        };
$s = '123';             # PadWalker の時は、ここの変数名は取れていなかった
$s = \$s;
@a = (
       '1',
       '2',
       '3',
       '4'
     );
%h = (
       'A' => '1',
       'B' => '2'
     );
$VAR9 = 'our';          # our 変数は残念ながら変数名は取れていない
$VAR10 = \$VAR9;
$VAR11 = 'local';       # local, global 変数も同様
$VAR12 = \$VAR11;
$VAR13 = 'global';
$VAR14 = \$VAR13;
$VAR15 = sub { "DUMMY" };
$VAR16 = 'x';
$VAR17 = sub { "DUMMY" };

PadWalker の例との違いは(表示がDump 形式であるという大きな見た目上の違いとは別に)、$s がちゃんと取れている事です。
PadWalker 版のテストでは、$s は即値として評価されていました。\$s として渡して初めて'$s' と表示されたのでした。

まとめ

さあ、まとめたいと思います。
PadWalker を使い、いろいろ作り込む事で自分好みにあった表示が作れることはtest2.pl で示しました。
その気になればour 変数も表示できるようになるはずですので、そういうものを用意するのもいいでしょう。
しかし、local 変数やglobal 変数やサブルーチンの名前など、そもそも変数名を取得できない限界があります。

それならばこそ、Data::Dumper::Names の容易さはとても大きな魅力であると思いました。
モジュール名もピンと来るもので、いいですよね。

というわけで、私はData::Dumper::Names を推します!