perl 在grep中使用带块参数的圆括号会产生意外结果

zour9fqk  于 2023-04-12  发布在  Perl
关注(0)|答案(3)|浏览(145)

下面的代码的第一次打印没有打印预期的结果,尽管子例程作为块参数传递。它应该打印1,但打印的是2。第一个和第二个print语句之间的区别是什么?

my @arr = ("hello", "world");
print scalar(grep (sub {return "hello" eq $_}, @arr)); # return 2, not expected

print scalar(grep {"hello" eq $_} @arr); # return 1 as expected
fwzugrvs

fwzugrvs1#

grep采用BLOCKEXPR
在您的第二个(工作)公式中,您提供了一个块,该块按预期使用本地$_值进行计算。
在你的第一个公式中,你提供了一个表达式。这个表达式碰巧是一个匿名子例程,其中为每个被grepped的元素绑定了$_,但是 * 子例程本身不被求值 *。
如果输入列表是map而不是grep,你可以更清楚地看到发生了什么:

> print "[", join(",", map (sub {return "hello" eq $_}, @arr)), "]\n";
[CODE(0x14d82bdc8),CODE(0x14d82bdc8)]

> print "[", join(",", map {"hello" eq $_} @arr), "]\n";
[1,]

当然,你也可以将临时子例程作为grep表达式的一部分进行求值,但语法很快就变得非常漂亮:

print scalar(grep ((sub {return "hello" eq $_})->(), @arr)); # returns 1
xwmevbvl

xwmevbvl2#

grep不接受子例程。事实上,它甚至不是一个真实的的函数[1]。grep可以通过两种方式之一调用,它们实际上只是语法上的不同:使用显式块或表达式参数。

grep {"hello" eq $_} @arr;
grep("hello" eq $_, @arr);

对于第二种形式,作为第一个“参数”传递给grep的表达式不仅被计算一次,而且对数组的每个元素都计算一次。

print scalar(grep (sub {return "hello" eq $_}, @arr));

在您给出的示例中,表达式参数是sub { return "hello" eq $_ }。我并没有说子例程会被调用。子例程永远不会被调用)每个列表元素一次。grep返回任何块返回true的元素,“true”由标量值定义:
如果标量值未定义、为空字符串或数字0(或其等效字符串“0”),则在布尔意义上将其解释为FALSE,如果是其他值,则解释为TRUE。
子例程不是未定义的,不是空字符串,也不是数字零,所以子例程是真值,因此输入列表的每个元素都满足(平凡的)块。
[1]在名称混乱的functions page中,许多内置的Perl函数不是真实的的函数,实际上是语言关键字。sort是一个臭名昭著的例子。没有办法用纯Perl编写一个行为像sort的函数(因为它可以接受一个块,一个子例程名称,或者两者都不接受)。

4sup72z8

4sup72z83#

Perl的“函数”的语法有一点奇怪,它接受一个块参数,这是我的Perl烦恼之一。有一些事情是奇怪的,因为这是Perl的方式:

grep {...} @array;   # no comma, block argument

grep $_ == 4, @array # comma, expression argument

添加sub看起来不像Perl的块参数,因为这不是Perl解析事物的方式:

grep sub { $_ == 4} @array # missing comma, expression argument, compilation error

grep sub { $_ == 4} @array # comma, expression argument

但是当你使用这个特殊的块形式,去掉sub时,这是可行的,Perl知道如何解析这些特殊情况,因为Perl知道如何解析这些特殊情况:

$ perl -MO=Deparse -e 'my @f = grep { $_ == 4 } @array'
my(@f) = grep({$_ == 4;} @array);
-e syntax OK

$ perl -MO=Deparse -e 'my @f = grep $_ == 4, @array'
my(@f) = grep(($_ == 4), @array);
-e syntax OK

$ perl -MO=Deparse -e 'my @f = grep sub{$_ == 4}, @array'
my(@f) = grep(sub {
    $_ == 4;
}
, @array);
-e syntax OK

$ perl -MO=Deparse -e 'my @f = grep sub{$_ == 4} @array'
Array found where operator expected at -e line 1, near "} @array"
    (Missing operator before  @array?)
syntax error at -e line 1, near "} @array
"
-e had compilation errors.

我希望Perl能对匿名函数有一个更一般的概念,这是Raku解决的问题之一。我认为Ruby在可选块方面也做得很好。
现在,让我们使用prototypes来创建我们自己的函数f,它带有一个block参数(这通常是最好的主意)。对于用户定义的函数来说,情况与Perl的内置函数略有不同(我知道这让人抓狂):
给予f一个区块,没问题:

$ perl -MO=Deparse -e 'sub f (&) { $_[0]->() }; print f {137}'
sub f (&) {
    $_[0]->();
}
print f(sub {
    137;
}
);
-e syntax OK

给予f一个匿名的sub,没问题:

$ perl -MO=Deparse -e 'sub f (&) { $_[0]->() }; print f sub {137}'
sub f (&) {
    $_[0]->();
}
print f(sub {
    137;
}
);
-e syntax OK

但是,使用parens和Perl认为块是一个匿名散列,即使你试图欺骗Perl将其视为一个代码块:

$ perl -MO=Deparse -e 'sub f (&) { $_[0]->() }; print f({137})'
Type of arg 1 to main::f must be block or sub {} (not anonymous hash ({})) at -e line 1, near "})
"
-e had compilation errors.
sub f (&) {
    $_[0]->();
}
print &f({137});

$ perl -MO=Deparse -e 'sub f (&) { $_[0]->() }; print f({137;})'
syntax error at -e line 1, near ";}"
-e had compilation errors.
sub f (&) {
    $_[0]->();
}

$ perl -MO=Deparse -e 'sub f (&) { $_[0]->() }; print f({return 137})'
Type of arg 1 to main::f must be block or sub {} (not anonymous hash ({})) at -e line 1, near "})
"
-e had compilation errors.
sub f (&) {
    $_[0]->();
}
print &f({(return 137)});

有时候事情就是这样。

相关问题