使用grep中带有括号的块参数会产生意外结果。

huangapple go评论65阅读模式
英文:

Use of parentheses with a block argument in grep produces unexpected outcome

问题

以下是您要翻译的内容:

第一个代码打印不出期望的结果,尽管子例程作为块参数传递。预期打印1,但实际打印2。第一个和第二个打印语句之间有什么区别?

my @arr = ("hello", "world");
print scalar(grep (sub {return "hello" eq $_}, @arr)); # 返回2,而非预期

print scalar(grep {"hello" eq $_} @arr); # 返回预期的1
英文:

The first print of the following code does not print the expected result although the subroutine is passed as the block argument. It is expected to print 1 but print's 2. What is the difference between the first and second print statements?

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

答案1

得分: 8

grep接受BLOCKEXPR。在你的第二个(有效)表达中,你提供了一个BLOCK,它会按预期使用本地的$_ 值进行评估。在你的第一个表达中,你提供了一个表达式。这个表达式恰巧是一个带有$_绑定的匿名子例程,用于对每个被过滤的元素,但是_子例程本身没有被评估_。如果你使用map而不是grep输入列表,你可以更清楚地看到发生了什么:```perl
> print "[", join(",", map (sub {return "hello" eq $_}, @arr)), "]\n";
[CODE(0x14d82bdc8),CODE(0x14d82bdc8)]

> print "[", join(",", map {"hello" eq $} @arr), "]\n";
[1,]
当然,你可以将临时子例程作为grep表达式的一部分进行评估,但语法会非常复杂:perl
print scalar(grep ((sub {return "hello" eq $
})->(), @arr)); # 返回 1


<details>
<summary>英文:</summary>

[`grep`](https://perldoc.perl.org/functions/grep) takes either a `BLOCK` or an `EXPR`.

In your second (working) formulation, you&#39;re providing a block, which evaluates with the local `$_` value as expected.

In your first formulation, you&#39;re providing an expression. The expression happens to be an anonymous subroutine with `$_` bound in it for each element being grepped, but _the subroutine itself is not evaluated_.

You can see what&#39;s going on more clearly if you were to `map` rather than `grep` the input list:
```perl
&gt; print &quot;[&quot;, join(&quot;,&quot;, map (sub {return &quot;hello&quot; eq $_}, @arr)), &quot;]\n&quot;;
[CODE(0x14d82bdc8),CODE(0x14d82bdc8)]

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

You could of course evaluate the ephemeral subroutine as part of the grep expression, but the syntax gets very beautiful very quickly:

print scalar(grep ((sub {return &quot;hello&quot; eq $_})-&gt;(), @arr)); # returns 1

答案2

得分: 6

Perl的语法对于接受一个代码块参数的“函数”有些奇怪,这是我在Perl中感到困扰的一点之一。有一些事情之所以奇怪,只是因为Perl就是这样做的:

grep {...} @array;   # 无逗号,代码块参数

grep $_ == 4, @array # 逗号,表达式参数

添加sub看起来对Perl来说不像是一个代码块参数,因为Perl解析方式不是这样的:

grep sub { $_ == 4} @array # 缺少逗号,表达式参数,编译错误

grep sub { $_ == 4} @array # 逗号,表达式参数

但是当你在这个特殊的代码块形式中省略了sub时,它就会起作用,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一个代码块,没问题:

$ 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

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

有时候,事情就是这样的。

英文:

Perl's syntax for its "functions" that take a block argument is a bit weird, and it's one of my Perl annoyances. There are some things that are just weird because that's how Perl does it:

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

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

Adding the sub doesn't look like a block argument to Perl simply because that's not the way that Perl parses things:

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

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

But this works when you use this special block form with the sub left off, and Perl knows how to parse these special cases because Perl knows how to parse these special cases:

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

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

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

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

That's just how it is. I wish Perl had a more general idea of anonymous functions, and that's one of the things that Raku addressed. I think Ruby did a nice job with optional blocks, too.

Now, let's make our own function, f with a block argument by using prototypes (which is more often than not a the best idea). The situation is slightly different (maddening, I know) for a user-defined function than Perl's builtin stuff:

Give f a block, no problem:

$ perl -MO=Deparse -e &#39;sub f (&amp;) { $_[0]-&gt;() }; print f {137}&#39;
sub f (&amp;) {
	$_[0]-&gt;();
}
print f(sub {
	137;
}
);
-e syntax OK

Give f a anonymous sub, no problem:

$ perl -MO=Deparse -e &#39;sub f (&amp;) { $_[0]-&gt;() }; print f sub {137}&#39;
sub f (&amp;) {
	$_[0]-&gt;();
}
print f(sub {
	137;
}
);
-e syntax OK

But, use parens and Perl thinks the block is an anonymous hash, even if you try to trick Perl into seeing it as a code block:

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

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

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

And sometimes that's just the way it is.

答案3

得分: 4

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] 在混乱命名的函数页面中,许多内置的 Perl 函数实际上不是真正的函数,而是实际上是语言关键字。 sort 就是一个臭名昭著的例子。纯 Perl 中没有任何方法可以编写类似于 sort 的函数(它可以接受块、子例程名称或两者都不接受)。

英文:

grep doesn't take a subroutine. In fact, it's not even a real function<sup>[1]</sup>. grep can be called in one of two ways, and they really only differ syntactically: With an explicit block, or with an expression argument.

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

You've already figured out the first form. For the second one, the expression you pass as the first "argument" to grep gets evaluated, not only once, but once per element of the array.

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

In the example you gave, the expression argument is sub { return &quot;hello&quot; eq $_ }. That expression will get evaluated (the expression itself with be evaluated; I did not say the subroutine will get called. The subroutine will never get called) once per list element. grep returns any elements for which the block returns true, and "true" is defined by Scalar values:

> A scalar value is interpreted as FALSE in the Boolean sense if it is undefined, the null string or the number 0 (or its string equivalent, "0"), and TRUE if it is anything else.

And a subroutine is not undefined, the null string, or the number zero, so a subroutine is a true value. Hence, every element of the input list satisfies the (trivial) block.


<sup>[1]</sup> Many of the built-in Perl functions in the confusingly-named functions page are not real functions and are in fact language keywords. sort is an infamous example. There is no way to write a function in pure Perl that behaves like sort (in that it can take a block, a subroutine name, or neither).

huangapple
  • 本文由 发表于 2023年4月11日 05:56:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/75981035.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定