方法修饰符和 glob 赋值

Method modifiers and glob assignments

有时,Moose 的方法修饰符不能很好地与其他包创建的符号 table 条目一起使用,这些包试图以自己的方式做类似 Moose 的事情。

我正在使用一些遵循这种模式的旧代码:

package MethodCreator;

sub make_some_method {
    my $caller = caller();
    *{$caller . '::generated_method'} = sub { print 'I am a generated method' }
}

1;

MethodCreator 包的目的是向多个消费者包添加一些标准定义,它通过直接 glob 分配实现这一点。问题是,这些创建的方法不能很好地与 Moose 的方法修饰符一起使用:

package Consumer;

use Moose;
use MethodCreator;

MethodCreator::make_some_method();

# The following line causes compilation to fail                                                                                                                                                              
# before generated_method => sub { print 'About to call a generated method: ' };                                                                                                                             

generated_method();

1;

如注释所示,尝试对这些动态添加的子例程之一使用方法修饰符会导致编译时错误 ("generated_method is not in the inheritance hierarchy")。

更改或替换 MethodCreator 是不切实际的(尽管可能是 "right solution")。所以问题是:如何更改包 Consumer 以使 'before' 修饰符与此类子例程配合得很好,即,如果 'generated_method' 直接在 Consumer 中定义,则表现得像您期望的那样?

您可以使用 Class::Method::Modifiers 来做到这一点,而不是使用内置的 Moose 修饰符。但是请确保您没有从它们导入任何内容,否则您将收到 redefined 警告。相反,从 Class::Method::Modifiers 包中显式调用 before

package MethodCreator;
use strict;
use warnings;
no strict 'refs';

sub make_some_method {
    my $caller = caller();
    *{$caller . '::generated_method'} = sub { print 'I am a generated method' }
}

package Consumer;

use Moose;
use Class::Method::Modifiers ();

MethodCreator::make_some_method();

# This one works
Class::Method::Modifiers::before( generated_method => sub { print 'About to call a generated method: ' });

generated_method();

1;

输出:

About to call a generated method: I am a generated method

现在为什么这样做了?

The C::M::M docs这样说:

Note that the syntax and semantics for these modifiers is directly borrowed from Moose (the implementations, however, are not).

简化,这会用它自己的子程序覆盖包中的子程序,它会执行您的操作并在之后调用原始程序。

另一方面,在 Moose 中,这些是在 Class::MOP::Method::Wrapped 中实现的,它们使用 MOP 来处理所有花哨的继承内容以及多个修饰符。但是因为你的 'manually generated' subroutine/method 没有这些,它们将无法工作。

编译子程序的包与子程序相关联。

$ perl -e'
   package Abc;
   use Devel::Peek;
   Dump(sub { });
'
SV = IV(0xf54988) at 0xf54998
  REFCNT = 1
  FLAGS = (TEMP,ROK)
  RV = 0xf54a88
  SV = PVCV(0xf70aa8) at 0xf54a88
    REFCNT = 2
    FLAGS = (PADMY,ANON,WEAKOUTSIDE,CVGV_RC)
    COMP_STASH = 0xf72818       "Abc"
    START = 0xf7a5d0 ===> 0
    ROOT = 0xf7a6e0
    GVGV::GV = 0xf80b68 "Abc" :: "__ANON__"       <-----
    FILE = "-e"
    DEPTH = 0
    FLAGS = 0x490
    OUTSIDE_SEQ = 94
    PADLIST = 0xf72830
    PADNAME = 0xf72848(0xf7ab90) PAD = 0xf72998(0xfacb90)
    OUTSIDE = 0xf54bd8 (MAIN)

显然,Moose 的 before 对此进行了检查。您无法更改此设置,因此如果您想继续使用 Moose 的 before.

,则必须按如下方式更改 make_some_method
sub _install_method {
    my ($pkg_name, $sub_name, $sub) = @_;
    eval("
       package $pkg_name;
       sub $sub_name { &$sub }
       return 1;
    ")
       or die($@);
}

sub make_some_method {
   _install_method(
      scalar(caller()),
      generated_method => sub {
         print 'I am a generated method';
      },
   );
}

你能假设 $caller 是一个 Moose class 吗?[1] 如果是这样,你可以用 Moose 方式创建方法,

$caller->meta->add_method(generated_method => sub {
    print "I am a generated method\n";
});

如果执行生成的代码可以依赖于 Moose,但调用代码可能是也可能不是 Moose class,你可以说

Class::MOP::Class->initialize($caller)->add_method(generated_method => sub {
    print "I am a generated method\n";
});

这将适用于任何类型的 Perl class。

[1] 或 Moo class。但不是继承自非 Moose 非 Moo class!

的 Moose 或 Moo class