Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:
Dear Monks,
I was reading up on the now-experimental given and thinking about using if instead for now.
print "$_ is ", do {
if ($_==0) { "zero" }
elsif ($_>0) { "pos" }
else { "neg" }
}, "\n" for -1..1;
# Output:
# -1 is neg
# 0 is zero
# 1 is pos
So it looks like the if evaluates to the value of the last statement of the executed block (at least on my v5.10.1, in case that makes a difference). This seems to make sense to me, since it's kind of like the return value of subs and evals being the value of the last statement.
(As an interesting side note, a few quick tests show that an if without an else returns "".
Also, do { for (0..5) { "F($_)" } } generates a warning about the string being in void context, and returns nothing, so the above doesn't seem to be true for all compound statements.)
My question is: Is this documented behavior, and if yes, where is it documented? I read the relevant-seeming sections of perlsyn and skimmed over the rest, did a bit of googling, and no obvious place has yet popped out, even though it feels like this should be stated somewhere - especially since the return value of given is documented in detail.
P.S. If I'm just having a particularly dense moment here and missed the place where it's documented, my apologies in advance.
P.P.S. I'm aware that the above style of code might not be the easiest to read, my question just comes from reading the given docs on its return value.
Re: return value of "if" (documentation?)
by davido (Cardinal) on Jan 07, 2014 at 23:18 UTC
|
Re-read the documentation in perlsub (Link updated; thanks ikegami.) and you will see two big red flags. First:
If no return is found and if the last statement is an expression, its value is returned.
In this construct: my $val = do { if(0){1} };, the last statement evaluated is the conditional, which is numeric zero, so $val gets 0. Had the conditional been true, the last expression evaluated would be the last one in the if statement's block (1, in this case). So unless you explicitly specify an "else" condition, your last expression will be the conditional from the if() statement, if that condition proves false.
The next red flag:
If the last statement is a loop control structure like a foreach or a while , the returned value is unspecified.
So now if your conditional looks like this: my $val = do { if( 1 ) { for( 0 ) { $_ } } };, all bets are off; the behavior is unspecified: You might get a '0' (the last expression), or not... or your keyboard may burst into flames, though Perl isn't prone to doing that under most circumstances. ;)
do{}; blocks can be thought of as immediate-execution subroutines, with implicit(-only) return values.
This tricky behavior is the reason for the recommended ban on "Implicit Returns" in Perl Best Practices (page 197).
| [reply] [d/l] [select] |
|
or your keyboard may burst into flames, though Perl isn't prone to doing that under most circumstances.
This guy might disagree ;-)
| [reply] |
|
I can't find either quotes in the linked document? You're quoting perlsub, not perlsyn.
An if statement is neither an expression nor a loop control structure, so neither quoted passages apply (wherever you quoted them from). perlsyn simply does not cover the situation. You should consider it unspecified as well.
| [reply] |
|
You're right; I intended to link to perlsub. I still think that it's applicable, though I would be in full agreement that the documentation ought to be more explicit about it, unless specifying the behavior more explicitly locks future p5p development into a behavior that they would prefer to change.
Update: I've submitted the following perlbug:
-----------------------------------------------------------------
[Please describe your issue here]
perlsub says this about implicit returns:
If no return is found and if the last statement is an
expression, its value is returned. If the last statement is
a loop control structure like a foreach or a while , the
returned value is unspecified.
Now consider the following code:
$x = 1;
sub foo { if( $x ) { 0 } }
print foo(), "\n";
The output will be 0, presumably because the last statement
is the expression, numeric '0'.
How about this:
$x = 0;
sub foo { if( $x ) { 1 } }
print foo(), "\n";
The output will be 0, presumably because the last expression
to be evaluated is '$x', which has a value of 0. But the last
statement to execute is literally "if()". If we run this past
B::Concise we find that the construct is converted into
something similar to sub { $x and 1 }, so it is intuitive that
the return value will be $x if false, or 1 if $x is true.
Although this behavior is possibly a little confusing to
someone who doesn't read between the lines in perlsub, it seems
reasonably stable (it's been with us forever), and unlikely to
change in the future. Therefore, perlsub should state the
following:
If no L<return> is found and if the last statement is an
expression, its value is returned. If the last statement
is an C<if( CONDITION ) { BLOCK }> construct, the value
of the return value will come from C<BLOCK> if C<CONDITION>
is true, or from C<CONDITION> if C<CONDITION> is false.
Relying on this behavior is detrimental to code legibility.
If the last statement is a loop control structure like a
C<foreach> or a C<while>, the returned value is unspecified.
[Please do not change anything below this line]
-----------------------------------------------------------------
---
If it gains the consideration of p5p I anticipate they will want to rehash the wording a bit before proceeding, or possibly simply make the if(){} construct explicitly unspecified, as has been done with loop constructs.
| [reply] [d/l] [select] |
|
... the last statement evaluated is the conditional...
do{}; blocks can be thought of as immediate-execution subroutines, with implicit(-only) return values.
These two things in particular cleared things up for me very well, thank you very much!
The second one also clears up why an empty do{} returns undef or the empty list depending on context. Although now I can't help but wonder where that is documented, if at all...?
| [reply] [d/l] [select] |
|
| [reply] |
Re: return value of "if" (documentation?)
by LanX (Sage) on Jan 07, 2014 at 23:33 UTC
|
AFAIK are if and and opcode-wise identical
lanx@nc10-ubuntu:~$ perl -MO=Deparse -e ' print if $a '
print $_ if $a;
-e syntax OK
lanx@nc10-ubuntu:~$ perl -MO=Deparse,-p -e ' print if $a '
($a and print($_));
-e syntax OK
This and short-circuiting should explain the returned values.
Cheers Rolf
( addicted to the Perl Programming Language)
| [reply] [d/l] [select] |
|
Deparse tries to reproduce code, while Concise shows what's actually there.
A more accurate demonstration:
$ perl -MO=Concise,-exec -e'print if $a'
1 <0> enter
2 <;> nextstate(main 1 -e:1) v:{
3 <#> gvsv[*a] s
4 <|> and(other->5) vK/1
5 <0> pushmark s
6 <#> gvsv[*_] s
7 <@> print vK
8 <@> leave[1 ref] vKP/REFC
-e syntax OK
$ perl -MO=Concise,-exec -e'$a and print'
1 <0> enter
2 <;> nextstate(main 1 -e:1) v:{
3 <#> gvsv[*a] s
4 <|> and(other->5) vK/1
5 <0> pushmark s
6 <#> gvsv[*_] s
7 <@> print vK
8 <@> leave[1 ref] vKP/REFC
-e syntax OK
But the question is about the if statement.
$ perl -MO=Concise,-exec -e'if ($a) { print }'
1 <0> enter
2 <;> nextstate(main 3 -e:1) v:{
3 <#> gvsv[*a] s
4 <|> and(other->5) vK/1
5 <0> pushmark s
6 <#> gvsv[*_] s
7 <@> print vK
8 <@> leave[1 ref] vKP/REFC
-e syntax OK
| [reply] [d/l] [select] |
|
> Deparse tries to reproduce code,
True but B::Deparse has documented options to switch of the reconstruction of if from and-op. I chose one of the ways¹...
I also checked it with B::Terse before posting, just wanted to keep the post short.
Thanks for supporting my point. :)
> But the question is about the if statement.
Do you imply that a post-fix if is not a "statement" like pre-fix if ?
Cheers Rolf
( addicted to the Perl Programming Language)
¹)
-xLEVEL
Expand conventional syntax constructions into equivalent ones that exp
+ose their internal operation.
...
If LEVEL is at least 7, "if" statements will be translated into
equivalent expressions using "&&", "?:" and "do {}"; for instance
lanx@nc10-ubuntu:~$ perl -MO=Deparse,-x7 -e 'if($a){print $b}'
$a and do {
print $b
};
-e syntax OK
| [reply] [d/l] [select] |
|
Re: return value of "if" (documentation?)
by pemungkah (Priest) on Jan 09, 2014 at 01:18 UTC
|
For your construction, I'd recommend a chained ternary expression:
print "$_ is ",
$_ == 0 ? 'zero'
: $_ > 0 ? 'pos'
: 'neg',
"\n" for -1..1;
The ternary operator ?: is essentially an if statement designed to return a value in a guaranteed-defined way.
Breaking this down:
- $_ == 0 ? 'zero' : ... does 'this is zero or something else'
- $_ > 0 ? 'pos' : 'neg' does the 'something else': positive or negative
I've inserted the line breaks in this particular way to make it easy to see how the chain of logical expressions is evaluated. This also makes it easy to see where to edit new tests in:
print "$_ is ",
$_ == 0 ? 'zero'
: $_ == 2 ? 'two'
: $_ == -1 ? 'minus one'
: $_ > 0 ? 'pos'
: 'neg',
"\n" for -2..2;
| [reply] [d/l] [select] |
|
|