2

我想在一个类中动态定义方法。我正在编写一个跟踪器,比下面的骨架稍微复杂一些,它也可以感知状态,但这与我的问题无关。我用调用 sprintf 的跟踪方法编写了一个 TraceSlave 类,用文本 \n 替换换行符,一切都很好。

基本上我想将我的跟踪实例化为:

my @classes = qw(debug token line src match);
my $trace = Tracer->new(\@classes);

而且我应该能够将动态定义的跟踪方法称为:

$trace->debug("hello, world");
$trace->match("matched (%s)(%s)(%s)(%s)(%s)", $1, $2, $3, $4, $5);

所以我的 Tracer 类看起来像:

package Tracer;
  sub new {
    my $class = shift;
    my $self = {};
    my @traceClasses = @{$_[0]};
    bless $self, $class;
    for (@traceClasses) {
# This next line is wrong, and the core of my question
      $self->$_ = new TraceSlave($_, ...)->trace
    } # for (@traceClasses)
  }

好吧,它不是因为那不编译。基本上我想定义 Tracer 实例的方法,作为 TraceSlave 实例的跟踪方法;在一个循环中。

我可以使用 AUTOLOAD 或 eval 来完成,但这是错误的。什么是正确的方法?

这是完整的 TraceSlave。没关系

package TraceSlave;
  sub new {
    my $self = { header => $_[1], states => $_[2], stateRef => $_[3] };
    bless $self, $_[0];
    return $self;
  } # new()

  sub trace {
    my $self = shift;
    my @states = @{$self->{states}};
    if ($states[${$self->{stateRef}}]) { # if trace enabled for this class and state
      my @args;
      for (1..$#_) { ($args[$_-1] = $_[$_]) =~ s/\n/\\n/g; } # Build args for sprintf, and replace \n in args
      print $self->{header}.sprintf($_[0], @args)."\n";
    }
  } # trace()
4

6 回答 6

2

每当我开始进入类的运行时修改时,我就开始使用 MOP 和 Moose。所以如果我没看错,你想要一些类似的东西

package Tracer;
use strict;
use warnings;
use Moose;
use TraceSlave;

has 'classes' => ( is => 'ro', isa => 'ArrayRef[Str]', required => 1 );

### This is to allow ->new(\@classes) invocation instead of just
### using ->new( classes => \@classes) for invocation
around BUILDARGS => sub {
  my $orig  = shift;
  my $class = shift;

  if ( @_ == 1 && ref $_[0] eq 'ARRAY' ) {
    return $class->$orig( classes => $_[0] );
  }
  else {
    return $class->$orig(@_);
  }
};

sub BUILD {
  my $self = shift;
  for my $class (@{$self->classes}) {
    my $tracer = TraceSlave->new($class, ...);
    $self->meta->add_method( $class => sub { $tracer->trace(@_) } );
  }
}

虽然我相当肯定这只是在引擎盖下做同样的事情,最终成为一个字符串评估。我根本没有深入研究 MOP 的内部结构。(我也不是 100% 确定这是正确的代码或使用 Moose 做事的最佳方式,所以买家要小心。:))

于 2013-06-06T17:20:54.643 回答
1

忽略“跟踪器”问题的细节,您只是希望能够为给定的包动态创建方法是否正确?那这个呢?

sub new {
    my ($class, $trace_classes) = @_;

    # ...

    foreach my $tc (@$trace_classes) {
        no strict 'refs';
        *{"${class}::${tc}"} = sub {
            my $self = shift;
            # ...
        };
    }

    return $self;
}

虽然在 a 中这样做似乎奇怪new!所以也许我错过了重点

于 2013-06-10T21:46:14.180 回答
0

好吧,我已经完成了我的小模块,并且学到了很多关于参考的知识。我正在动态定义方法,但是使用我认为很脏的 eval 。然而,似乎没有人有更好的主意,所以就在这里。

创建动态方法的那行就在注释之后: # 动态方法的创建非常脏,肯定有更好的方法吗?

所以我仍然有兴趣听到更好的方法。正如有人说我可以使用 Moose,但 Moose 无论如何都会为我做一个评估。

#!/usr/bin/perl
use strict;
use warnings;

package TraceSlave;
  sub new {
    my $self = { header => $_[1], states => $_[2], stateRef => $_[3] };
    bless $self, $_[0];
    return $self;
  } # new()

  sub trace {
    my $self = shift;
    if ($self->{states}->[${$self->{stateRef}}]) {  # if trace enabled for this class and state
      my @args;
      for (1..$#_) { ($args[$_-1] = $_[$_]) =~ s/\n/\\n/g; } # Build args for sprintf, and replace \n in args
      print $self->{header}.sprintf($_[0], @args)."\n";
    }
  } # trace()

package Tracer;
  sub new {
    my ($class, $classList, $stateCount, $stateRef) = @_;
    my $self = { states => {}, slaves => [], count => $stateCount };
    my @classes = split(/\s+/, $classList);
    my $maxlen = 0;

    for (@classes) { # loop over all trace classes to find longest
      my $len = length($_);
      $maxlen = $len if $len > $maxlen;
    }
    $maxlen++; # Add a space

    for (0..$#classes) { # loop over all trace classes, and eval create a slave for each class
      my $tc = $classes[$_];
      my $states = $self->{states}->{$tc} = [];
      for (0..$stateCount) { $states->[$_] = 0; }
      $self->{slaves}[$_] = TraceSlave->new( "$tc:"." "x($maxlen-length($tc)), $states, $stateRef );
# Very dirty creation of dynamic method, surely there's a better way?
      eval("sub $tc { ".'$self=shift; $self->{slaves}['.$_.']->trace(@_); }');
    } # for (0..$#classes)
    bless $self, $class;
    return $self;
  } # new()

  sub _onOff { # switch on traces
    my ($self, $onOff, $classList, $statesRef) = @_;
    my @classes = split(/\s+/, $classList);
    my $count = $self->{count};

    for (@classes) { # loop over all supplied trace classes and switch on/off for state list
      my $states = $self->{states}->{$_};
      if ($statesRef) { for (@$statesRef) { $states->[$_] = $onOff; } }
      else { for (0..$count) { $states->[$_] = 1; } } # switch on for all states is no state list
    } # for (0..$#classes)
  } # on()

  sub on {
    my $self = shift;
    $self->_onOff( 1, @_ );  
  }

  sub off {
    my $self = shift;
    $self->_onOff( 0, @_ );  
  }
1;
于 2013-06-10T20:36:08.323 回答
0

我通常使用字符串 eval 来定义子程序:

for my $method (@classes) {
    eval "sub $method { 'TraceSlave'->new('$method', ...)->trace }";
}
于 2013-06-06T10:52:36.493 回答
0

@Unk 回答上面 Unk 的小代码片段。

  foreach my $tc (@classes) { # loop over all trace classes, and create a slave for each class
    my $states = $self->{states}->{$tc} = [];
    $slave = TraceSlave->new( "$tc:"." "x($maxlen-length($tc)), $states, $stateRef );
    no strict 'refs';
    *{"${class}::$tc"} = sub { $slave->trace(@_[1..$#_]); }
  } # foreach my $tc (@classes)
  bless $self, $class;
  return $self;
} # new()

这当然要干净得多。我不再需要在 Tracer $self 中有奴隶了。

但是我应该能够将整个子替换为:

*{"${class}::${tc}"} = $slave->trace;

这当然不起作用,因为我需要引用 $slave->trace,上面的代码只会调用它。可悲的是,我也不了解 glob 的使用,或者实际上是 lvalue 上的许多参考资料。在我的辩护中,我可以用 C 中的指针或 javascript 中的引用来做任何事情,但是 perl 引用是一顿美餐。还在学习。

我认为 OO 是正确的方法,因为跟踪器有很多私有数据,特别是哪个跟踪类针对哪个状态,当然还有每个跟踪类的标头。如果我能让上面的简单分配工作,嵌套对象也是正确的。

所有这些都是我为我的 VHDL 项目编写 make 系统的一部分。我强迫自己用 perl 来做这件事,因为我认为是时候正确地学习这门语言了,而不是写 10 行胶带。

我现在意识到 *{"${class}::${tc}"} = \&trace->slave 可能永远无法工作。第一个参数是 Tracer 实例还是 TraceSlave 实例?要工作,它必须是 TraceSlave 实例,但这是在 Tracer 类中定义的方法。当然,我可以将奴隶放回 Tracer $self 中,但这会使事情变得更加复杂。

我想它现在尽可能简单地做我想要的。

于 2013-06-11T08:32:05.057 回答
0

如果修改包的符号表以向类添加方法,则不能有两个具有相同命名方法的不同语义的实例。在这种情况下使用 AUTOLOAD 对我来说似乎非常好。

问候,马蒂亚斯

于 2013-06-10T20:46:23.147 回答