Perl 将子例程传递给模块并重新定义它?

Perl 将子例程传递给模块并重新定义它?,perl,Perl,我试图用一个方法创建一个模块,该方法接收子例程并重新定义它。我在主脚本中重新定义子例程没有问题,但在方法中似乎没有相同的语法: main.pl 下午四时 测试输出: ReDef::ReDef()不重新定义。在我看来,*ref是一个coderef,给它分配另一个子例程应该会改变main::orig() 正确的语法是什么?这对我很有用: use v5.16; use strict; use warnings; package Redef; sub redef { my $ref = sh

我试图用一个方法创建一个模块,该方法接收子例程并重新定义它。我在主脚本中重新定义子例程没有问题,但在方法中似乎没有相同的语法:

main.pl 下午四时 测试输出: ReDef::ReDef()不重新定义。在我看来,*ref是一个coderef,给它分配另一个子例程应该会改变main::orig()

正确的语法是什么?

这对我很有用:

use v5.16;
use strict;
use warnings;

package Redef;

sub redef {
    my $ref = shift;
    ${$ref} = sub { say "Redefined!"; }
}

package main;

my $orig = sub { say "Original!"; };
Redef::redef(\$orig);
$orig->(); # Redefined!
虽然这只是反复试验的结果,但我很高兴看到更好的答案


可能让您感到困惑的是typeglob运算符,
*
。在Perl中,使用sigil(
${$scalar\u ref}
@{$array\u ref}
)取消引用,
*
运算符用于符号表技巧,在您的例子中也可以使用,请参见@tobyink的答案。

redef
函数应该是这样的:

package ReDef;
use strict;
use warnings;
sub redef {
   my $ref = shift;
   no warnings qw(redefine);
   *$ref = sub { print "Redefined!" };
}
ReDef::redef(\&orig);
ReDef::redef(\*orig);
你应该而不是这样称呼它:

package ReDef;
use strict;
use warnings;
sub redef {
   my $ref = shift;
   no warnings qw(redefine);
   *$ref = sub { print "Redefined!" };
}
ReDef::redef(\&orig);
ReDef::redef(\*orig);
相反,您必须这样称呼它:

package ReDef;
use strict;
use warnings;
sub redef {
   my $ref = shift;
   no warnings qw(redefine);
   *$ref = sub { print "Redefined!" };
}
ReDef::redef(\&orig);
ReDef::redef(\*orig);
为什么??调用
orig
时,您正在通过符号表查找名称“orig”,因此
redef
函数需要更改符号表,以便将该名称指向不同的代码位。Globrefs基本上是指向符号表的一点点位的指针,所以这就是您需要传递给
ReDef::ReDef
的内容

打个比方,想象一下,当你想知道刘易斯战役的日期时,你的程序是去图书馆,在目录中查找一本关于13世纪英国战役的书的书架地址,去那个书架,然后查找日期。。。瞧,1264年5月14日!现在,想象一下我想给你提供修改过的信息。简单地定义一个新的coderef就像是把一本新书放在书架上:它不会欺骗你,因为目录仍然指向旧书。我们也需要修改目录

更新

您可以使用原型使其更美观。原型通常不被推荐,但这似乎是一个非邪恶的使用他们

use strict;
use warnings;

sub ReDef::redef (*) {
   my $ref = shift;
   no warnings qw(redefine);
   *$ref = sub { print "Redefined!\n" };
}

sub orig { print "Original!\n" }
orig;

ReDef::redef *orig;  # don't need the backslash any more
orig;

谢谢,这似乎对我也有用。但是${$ref}到底是什么?${$ref}只是取消引用$ref。但是您必须注意,zoul并没有像您那样定义一个子例程,而是通过使用$orig=sub{…}
$ref
计算出一个引用(对一个代码引用),
$$ref
(=
$$ref}
)计算为代码引用。这意味着,
$
在这里是一个解引用操作符,如果我没弄错的话。谢谢!如果我理解正确,您的解决方案会更改符号表,以便将符号表中的
orig
记录更改为
ReDef
中的内容。然而,我的解决方案在
main::
中保留了一个普通的coderef,改变的是coderef的值,而不是它的符号表记录。zoul,是的,在您的解决方案中,变量
$orig
得到了一个新的值,这很好,但是询问者似乎没有将代码保留在引用中,而是保留在符号表中(这是一件非常正常的事情!)你是对的,这似乎更适合我们尝试做的事情。现在,如果我改变redef子例程,在重新定义之前调用原来的子例程:${$ref}->(),它似乎按预期工作,取消对子例程的引用。这是正确的方法吗?我注意到了一个可能的副作用:当我使用Data::Dumper打印重新定义的子例程的解析代码时,ReDef包声明以及use语句出现在开头。这是声明anon的副作用吗模块内部有多个子程序?如果我想从另一个模块中重新定义一个子程序,而该模块本身使用其他一些模块,这会带来麻烦吗?
B::Deparse
(用于Deparse coderefs的
Data::Dumper
模块)通常会在输出中打印一个包声明和各种杂注。这就是“调用者”函数中的信息和编译时提示是正确的。不要让它打扰您-
B::Deparse
做的事情是正确的。