8

私たちは大規模な Perl アプリケーションをオブジェクト指向インターフェイス、特にデータ モデル向けにゆっくりとリファクタリングしています。煩わしい部分は、スタック トレースの有用性が低下することです。捏造された例を挙げると:以前。

sub send_message {
    my ($user_id, $message) = @_;
    ...
    Carp::confess('test');
}

# output:
test at example.pm line 23
    foo('42', 'Hello World') called at example.pl line 5

後。

sub send_message {
    my ($user, $message) = @_;
    ...
    Carp::confess('test');
}

# output:
test at example.pm line 23
    foo('MyApp::Model::User=HASH(0x2c94f68)', 'Hello World') called at example.pl line 5

そのため、どのユーザーが に渡されたかはわかりませんfoo()。クラス名 (既に文書化されています) とオブジェクトのメモリ アドレスのみが表示されます。

overload.pm を使用して、モデル クラスに文字列化演算子をインストールしようとしました。

use overload ( '""' => \&stringify );

sub stringify {
    my ($self) = @_;
    return sprintf '%s[id=%d]', ref($self), $self->id;
}

しかし、これは longmess には影響しません。私が欲しいのは次のようなものです:

test at example.pm line 23
    foo('MyApp::Model::User[id=42]', 'Hello World') called at example.pl line 5

つまりfoo()、オブジェクトのstringify()メソッドを使用して最初のパラメータを表示する必要があります。どうすればそれを達成できますか?

4

1 に答える 1

11

問題は次の部分にありCarp.pmます。

sub format_arg {
    my $arg = shift;
    if ( ref($arg) ) {
        $arg = defined($overload::VERSION) ? overload::StrVal($arg) : "$arg";
    }
    ...
}

つまり、引数がオーバーロードされたオブジェクトである可能性がある場合、文字列化のオーバーロードはStrValhelperで回避され、デフォルトの文字列化が強制されます。

残念ながら、それを回避する簡単な方法はありません。できることは、Carp::format_argサブにモンキー パッチを適用することだけです。

BEGIN {
  use overload ();
  use Carp ();
  no warnings 'redefine';
  my $orig = \&Carp::format_arg;

  *Carp::format_arg = sub {
    my ($arg) = @_;
    if (ref $arg and my $stringify = overload::Method($arg, '""')) {
      $_[0] = $stringify->($arg);
    }
    goto &$orig;
  };
}

そのままでは、これは洗練されていないため、プラグマに入れる必要があります。

ファイルCarp/string_overloading.pm:

package Carp::string_overloading;

use strict; use warnings;

use overload ();
use Carp ();

# remember the original format_arg method
my $orig = \&Carp::format_arg;
# This package is internal to Perl's warning system.
$Carp::CarpInternal{ __PACKAGE__() }++;

{
    no warnings 'redefine';
    *Carp::format_arg = sub {
        my ($arg) = @_;
        if (    ref($arg)
            and in_effect(1 + Carp::long_error_loc)
            and my $stringify = overload::Method($arg, '""')
        ) {
            $_[0] = $stringify->($arg);
        }
        goto &$orig;
    };
}

sub import   { $^H{__PACKAGE__ . "/in_effect"} = 1 }

sub unimport { $^H{__PACKAGE__ . "/in_effect"} = 0 }

sub in_effect {
    my $level = shift // 1;
    return (caller $level)[10]{__PACKAGE__ . "/in_effect"};
}

1;

次に、コード

use strict; use warnings;

package Foo {
    use Carp ();

    use overload '""' => sub {
        my $self = shift;
        return sprintf '%s[%s]', ref $self, join ", ", @$self;
    };

    use Carp::string_overloading;
    sub foo { Carp::confess "as requested" }

    no Carp::string_overloading;
    sub bar { Carp::confess "as requested" }
}

my $foo = bless [1..3] => 'Foo';

eval { $foo->foo("foo") };
print $@;
eval { $foo->bar("bar") };
print $@;

出力:

as requested at test.pl line 12.
        Foo::foo('Foo[1, 2, 3]', 'foo') called at test.pl line 20
        eval {...} called at test.pl line 20
as requested at test.pl line 15.
        Foo::bar('Foo=ARRAY(0x85468ec)', 'bar') called at test.pl line 22
        eval {...} called at test.pl line 22
于 2013-08-23T09:43:06.757 に答える