Perl中如何调用由字符串生成的子例程引用?
日常Perl代码开发中,我需要反复执行以下操作:
- 根据子例程名称生成对应的存储文件路径
- 调用目标子例程,获取其返回内容的引用
- 将拿到的引用持久化存储到之前生成的路径下
为遵循DRY(不要重复自己)原则,我打算把这部分重复逻辑封装成通用函数。目前已经实现了在dontrepeatyourself函数内根据字符串生成对应子例程引用的逻辑,但尝试调用动态生成的子例程时,触发了如下报错:
Can't use string ("doggy") as a subroutine ref while "strict refs" in use
现有代码中的writeeverythingtwice函数可以正常实现需求,但调用时需要用户同时传入子例程名称、子例程引用两个参数,相当于重复输入两次子例程名。如果两次输入内容不一致,会引发隐蔽的逻辑错误,增加调试成本。我需要找到在dontrepeatyourself函数内部,仅通过传入的子例程名字符串直接调用对应子例程的正确实现方式,现有完整代码如下:
#!/usr/bin/env perl use strict; use warnings; use Data::Dumper qw(Dumper); $Data::Dumper::Sortkeys = 1; use feature 'say'; writeeverythingtwice(name=>'doggy',ref=>\&doggy,); dontrepeatyourself(subname=>'doggy',); sub dontrepeatyourself { use Cwd 'abs_path'; use Storable; my$whoiam= (caller(0))[3]; say 'you are now inside ',$whoiam; my$HOMEarg={@_}; say ref \&{ $HOMEarg->{subname} }; my $reftostore=\&{ $HOMEarg->{subname} }->(); # 这里触发报错 my$path=abs_path('.') . '/' . $HOMEarg->{subname} . '.perlstorable' ; store($reftostore,$path); say 'we have stored to ',$path; } sub writeeverythingtwice { use Cwd 'abs_path'; use Storable; my$whoiam= (caller(0))[3]; say 'you are now inside ',$whoiam; my$HOMEarg={@_}; my$path=abs_path('.') . '/' . $HOMEarg->{name} . '.perlstorable' ; my$reftostore=$HOMEarg->{ref}->(); store($reftostore,$path); say 'we have stored to ',$path; } sub doggy { my$whoiam= (caller(0))[3]; say 'you are now inside ',$whoiam; return [ 'foo','bar' ]; }
开启use strict 'refs'的场景下,Perl会禁止直接用字符串作为符号引用取符号表中的子例程,原代码的链式调用写法\&{ $HOMEarg->{subname} }->()没有做引用合法性校验,直接触发了严格模式的引用检查拦截。
用UNIVERSAL类提供的can方法获取子例程引用是最稳妥的方案,既不会触发strict refs的限制,还能自动校验对应名称的子例程是否存在,避免传入不存在的子例程名导致的运行时崩溃。
修复后的dontrepeatyourself函数代码如下:
sub dontrepeatyourself { use Cwd 'abs_path'; use Storable; my$whoiam= (caller(0))[3]; say 'you are now inside ',$whoiam; my$HOMEarg={@_}; my $subname = $HOMEarg->{subname}; # 校验子例程是否存在,获取对应的代码引用 my $sub_ref = __PACKAGE__->can($subname) or die "不存在名为 $subname 的子例程"; my $reftostore = $sub_ref->(); my$path=abs_path('.') . '/' . $subname . '.perlstorable' ; store($reftostore,$path); say 'we have stored to ',$path; }
替换原代码中的对应函数后,仅需传入subname参数即可完成全流程,不需要重复传入子例程引用,也不会触发strict模式的报错。如果需要调用其他包下的子例程,把__PACKAGE__替换成对应包名即可。
内容的提问来源于stack exchange,提问作者Jacob Wegelin

