U. *_*ndl 8 regex perl lazy-evaluation
(抱歉这个标题,但是这个“功能”确实让我困惑)
在学习 Perl 时,我了解到o使用变量的正则表达式的修饰符只会被评估一次,即使变量在初始评估后发生变化。最初看起来没有任何问题,这是明确指定的。
显然,在所使用的变量获得其值之前不能进行初始评估。
现在qr让生活变得更有趣了。考虑以下代码(也在定义其他变量的循环中执行):
{
my $n = $name;
$n =~ s/[^\w\.-]/_/g;
$n = qr:^${n}\@${another_variable}$:o;
@a = grep { !/$n/ } @a;
}
Run Code Online (Sandbox Code Playgroud)
当直接使用正则表达式时qr,人们可能会争辩说正则表达式只编译一次,即使变量的范围超出范围(超出范围是否被视为变量的更改?)
但是当使用qr构建正则表达式并将其分配给词法变量时,编译的正则表达式将超出范围,因此我期望正则表达式不能重用并且将被重新构建(基本思想是内部的正则表达式grep应该不必为每次迭代而重建)。
由于生活是残酷的,似乎引用的整个正则表达式$n从未被重建,因此使用第一个值直到程序停止。
有趣的是,在 Perl 5.18.2(正在使用的版本)中不再提及修饰符o,perlre(1)而 Perl 5.26.1 在相应的页面中说:
o - 假装优化你的代码,但实际上引入了错误
那么任何人都可以解释“一次”评估的规则(以及语义在 Perl 的生命周期中是否发生了变化)?
Perl 有几个结构,它们不仅将状态存储在变量中,还存储操作码本身的一些状态。除了正则/o表达式模式之外,这还包括..触发器运算符(在标量上下文中)或state变量。
也许state变量是最清楚的,因为它对应于static许多其他语言(例如C)中的局部变量。变量state在程序的生命周期内最多初始化一次。表达式state $var = initialize()可以理解为
my $var;
if (previously_initialized) {
$var = cached_value;
} else {
$var = initialize();
}
Run Code Online (Sandbox Code Playgroud)
这不会跟踪initialize()表达式中的依赖关系,而仅对其求值一次。
/.../o同样,将正则表达式模式视为一种隐藏状态变量也是有意义的state $compiled_pattern = qr/.../。
/o很久以前,当正则表达式被即时编译时,该功能是一个好主意,类似于它在其他语言中的工作方式,其中正则表达式模式作为字符串提供给搜索函数。
长期以来,出于性能目的,它并不是必需的,并且仅在进行变量插值时才有效。但如果您确实想要这种行为,那么使用state变量可以更清楚地传达该意图。因此,我认为修饰符没有适当的用途/o。
“很大程度上已过时/o”( perlop) 标志仍然具有“曾经”的含义和操作。虽然它几乎没有被提及,但它在 perlop 中perlre得到了解决
/PATTERN/msixpodualngc
...
... Perl 不会重新编译该模式,除非它包含的插值变量发生变化。您可以通过在尾部分隔符后添加/o(代表“一次”) 来强制 Perl 跳过测试并且从不重新编译。曾几何时,Perl 会不必要地重新编译正则表达式,为了提高速度,这个修饰符可以告诉它不要这样做。但现在,使用的唯一原因/o之一是:
- 这些变量有数千个字符长,并且您知道它们不会改变,并且您需要通过让 Perl 跳过对此的测试来提高最后一点速度。(这样做会带来维护上的损失,因为提及 /o 就意味着您不会更改模式中的变量。如果您确实更改了它们,Perl 甚至不会注意到。)
- 您希望模式使用变量的初始值,无论它们是否更改。(但是有比使用 /o 更明智的方法来实现这一点。)
- 如果模式包含嵌入代码,例如
Run Code Online (Sandbox Code Playgroud)use re 'eval'; $code = 'foo(?{ $x })'; /$code/那么 perl 每次都会重新编译,即使模式字符串没有改变,以确保
$x每次都能看到当前的值。/o如果您想避免这种情况,请使用。最重要的是,使用 /o 几乎从来都不是一个好主意。
因此,事实上,显然它甚至不会测试要插值的变量是否发生变化,这可能有合法的用途。但事实上,总而言之,它可能不应该被使用。
演示“一次”操作的示例
perl -Mstrict -wE'
sub tt {
my ($str, $v) = @_;
my $re = qr/$v/o;
$str =~ s/$re/X/;
return $str
};
say tt( q(a1), $_ ) for qw(a b c)'
Run Code Online (Sandbox Code Playgroud)
对于-ed 模式或正则表达式/o上的,每次都会匹配(更改)该字符串,即使仅在第一次迭代中为模式传递。显然,该模式不会重新编译,因为变量稍后具有和 then且不应该匹配。qra1abc
只有/o第一次迭代才有正则表达式匹配。
在每次迭代中,词法$re显然会随着整个函数的消失而消失,但其最初编译的值仍然会被使用。这意味着某些操作/o是“非常”全球化的。这种顽固的全球行为在其他一些古老的特征中并非闻所未闻(glob例如,我想到的)。
如果 sub 是词法变量中的代码引用,并且始终在动态作用域中重新创建,则情况也是如此。
perl -Mstrict -wE'
for my $c (qw(a b c)) {
my $tt = sub {
my ($str, $v) = @_;
my $re = qr/$v/o;
$str =~ s/$re/X/;
return $str
};
say $tt->( q(a1), $c )
}'
Run Code Online (Sandbox Code Playgroud)
全部打印X1三遍。