在grep中使用带块参数的括号会产生意想不到的结果

  • 本文关键字:结果 意想不到 参数 grep perl
  • 更新时间 :
  • 英文 :


虽然子例程作为块参数传递,但下面代码的第一次打印并不打印预期的结果。它应该输出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

grepBLOCKEXPR

在您的第二个(工作)公式中,您提供了一个块,它按预期使用本地$_值进行计算。

在第一个公式中,您提供了一个表达式。表达式恰好是一个匿名子例程,其中绑定了$_,用于每个被grep的元素,但是子例程本身不被求值

你可以更清楚地看到发生了什么,如果你是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

Perl函数的语法;接受block参数有点奇怪,这是我的Perl烦恼之一。有些东西很奇怪,因为Perl就是这么做的:

grep {...} @array;   # no comma, block argument
grep $_ == 4, @array # comma, expression argument

添加sub对Perl来说不像block参数,因为这不是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在可选块方面也做得很好。

现在,让我们通过使用原型(这通常是最好的主意)创建我们自己的函数f,并带有一个块参数。对于用户定义函数而言,情况与Perl的内置函数略有不同(我知道这很令人抓狂):

f一个block,没问题:

$ 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

但是,使用parent, 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)});

有时候事情就是这样。

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

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

你已经算出了第一种形式。对于第二个,作为第一个"参数"传递的表达式togrep被求值,不是一次,而是对数组的每个元素求值一次。

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

在您给出的示例中,表达式参数是sub { return "hello" eq $_ }。这个表达式会被求值(表达式本身会被求值;我没有而不是说子程序将被调用。对于每个列表元素,子例程永远不会被调用一次。grep返回block返回true的所有元素,以及"true"由标量值定义:

标量值在布尔意义上被解释为FALSE,如果它未定义,则为空字符串或数字0(或其等效字符串&;0&;),如果是其他任何值,则为TRUE。

子程序不是未定义的、空字符串或数字零,所以子程序是真值。因此,输入列表中的每个元素都满足(平凡)块。


[1]函数页面中的许多内置Perl函数并不是真正的函数,实际上是语言关键字。sort就是一个臭名昭著的例子。没有办法在纯Perl中编写一个行为类似sort的函数(因为它可以接受一个块,一个子例程名称,或者两者都不接受)。

最新更新