読者です 読者をやめる 読者になる 読者になる

Perl - Data::Dumper の冗長さを調整する

Use Data::Dump filters for nicer pretty-printing | The Effective Perler
を読んで、有益だと思ったのでそれをここにも書いておく。

次の Perl のコード

use DateTime;
use List::Util qw(shuffle);
use Data::Dumper;

my $data = {
    today => DateTime->now,
    array => [shuffle(1 .. 30)],
};

print Dumper $data;

を実行すると

$VAR1 = {
          'array' => [
                       1,
                       7,
                       19,
                       23,
                       24,
                       29,
                       9,
                       3,
                       18,
                       2,
                       12,
                       27,
                       6,
                       4,
                       16,
                       15,
                       22,
                       25,
                       13,
                       28,
                       5,
                       17,
                       20,
                       30,
                       8,
                       10,
                       26,
                       11,
                       21,
                       14
                     ],
          'today' => bless( {
                              'local_rd_secs' => 16240,
                              'local_rd_days' => 734540,
                              'rd_nanosecs' => 0,
                              'locale' => bless( {
                                                   'default_time_format_length' => 'medium',
                                                   'native_territory' => 'United States',
                                                   'native_language' => 'English',
                                                   'native_complete_name' => 'English United States',
                                                   'en_language' => 'English',
                                                   'id' => 'en_US',
                                                   'default_date_format_length' => 'medium',
                                                   'en_complete_name' => 'English United States',
                                                   'en_territory' => 'United States'
                                                 }, 'DateTime::Locale::en_US' ),
                              'local_c' => {
                                             'hour' => 4,
                                             'second' => 40,
                                             'month' => 2,
                                             'quarter' => 1,
                                             'day_of_year' => 38,
                                             'day_of_quarter' => 38,
                                             'minute' => 30,
                                             'day' => 7,
                                             'day_of_week' => 2,
                                             'year' => 2012
                                           },
                              'utc_rd_secs' => 16240,
                              'formatter' => undef,
                              'tz' => bless( {
                                               'name' => 'UTC'
                                             }, 'DateTime::TimeZone::UTC' ),
                              'utc_year' => 2013,
                              'utc_rd_days' => 734540,
                              'offset_modifier' => 0
                            }, 'DateTime' )
        };

と見るのも放棄したくなる結果が得られる。 Dumper は構造を知りたいがためにやってるのに、これじゃ構造がよく分からない。
そこで

  1. DateTime オブジェクトは 2012/02/07 12:30:00 +0900 のように表示すれば十分
  2. 配列の要素数が多いときは、そのうちいくつかを表示すれば十分

を実現させる。

どうやるかというと、 Data::Dumper じゃなくて Data::Dump モジュールの dumpf 関数を使う。この関数は第一変数に dump したい変数をとり、そして第二変数に表示のカスタマイズ方法を定義したサブルーチンのリファレンスを指定する。
詳しくは始めに挙げたサイトと

を参照してもらうこととして、上記2条件を満たすコードを書くと

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dump qw(dumpf);
use DateTime;
use List::Util qw(shuffle);

my $data = {
    today => DateTime->now,
    array => [shuffle(1 .. 30)],
};

print dumpf($data, \&get_filter);

BEGIN {
    my %filters = (
        DateTime => sub {
            require Storable;
            my $date = Storable::dclone(shift);
            $date->set_time_zone('local');
            $date->strftime('%Y/%m/%d %H:%M:%S %z');
        },
        ARRAY => sub {
            my @array = @{shift()};
            [@array[0, 1], "...", $array[-1]];
        },
    );

    sub get_filter {
        my ($ctx, $object_ref) = @_;
        if ($ctx->is_blessed and exists $filters{$ctx->class}) {
            my $string = $filters{$ctx->class}->($object_ref);
            return { dump => $string };
        }
        elsif ($ctx->is_array && @{$object_ref} > 4) {
            my $number_of_items = scalar @{$object_ref};
            my $object = $filters{$ctx->reftype}->($object_ref);
            return {
                object  => $object,
                comment => "some items hidden: "
                           ."actual number of items=$number_of_items",
            };
        }
        return undef;
    }
}

となる。これを実行すると

{
  array => # some items hidden: actual number of items=30
           [4, 9, "...", 7],
  today => 2012/02/07 13:41:35 +0900,
}

となる。素晴らしい!

ということで、複雑な構造をもつ変数を dump しても冗長過ぎて意味が理解しにくいときや、dump 方法を独自に決めたいときは、 Data::Dump モジュールの dumpf を使うといいと分かった。

今日の疑問

  • なんで BEGIN で括らないといけないのかはおいおい理解する。