The conditional (ternary) operator suggests the ternary operator is a substitute for if
... else
.
I always thought so, but recently I have a logical problem with that.
Consider this short debug session:
DB<1> $s='X'
DB<2> 1 ? $s .= '_' : $s = '_'
DB<3> x $s
0 '_'
So if 1
is true, then the expression $s .= '_'
should be evaluated (and not $s = '_'
).
But why is $s
just '_'
at the end?
The ternary conditional operator (?:
) has a higher precedence than the assignment operator (=
) (the table of the precedence of Perl's operator can be found in the Operator Precedence and Associativity section of perlop). As such, the line
1 ? $s .= '_' : $s = '_'
is parsed by Perl as
(1 ? ($s .= '_') : $s) = '_'
(You can check that by yourself by running perl -MO=Deparse <your program>
)
Note also that $s .= '_'
returns $s
with the added _
at the end, and that this $s
can be assigned to (in technical terms, it's an lvalue). This is documented in the Assignment Operators section of perlop:
Unlike in C, the scalar assignment operator produces a valid lvalue. Modifying an assignment is equivalent to doing the assignment and then modifying the variable that was assigned to.
So, basically, your code is doing
($s .= '_') = '_';
Which is equivalent to
$s .= '_';
$s = '_';
It is an issue of operator precedence.
$ perl -MO=Deparse,-p -e '
> $s='X';
> $t=1;
> $t ? $s .= '_' : $s = '_';
> print $s'
($s = 'X');
($t = 1);
(($t ? ($s .= '_') : $s) = '_');
print($s);
-e syntax OK
Whether the ternary condition is true or false, ultimately $s
is set to "_"
.
To do what you intend to do, you need to add at least one set of parentheses:
1 ? $s .= '_' : ($s = '_');
The conditional operator only evaluates what's necessary, but you have a precedence problem.
First of all, the conditional operator is indeed guaranteed to use short-circuit evaluation, meaning it only evaluates what's necessary.
$ perl -M5.010 -e'
sub f { say "f" }
sub g { say "g" }
$ARGV[0] ? f() : g();
' 0
f
$ perl -M5.010 -e'
sub f { say "f" }
sub g { say "g" }
say $ARGV[0] ? f() : g();
' 1
g
This is even true for E1 || E2
, E1 or E2
, E1 && E2
and E1 and E2
. They only evaluate their right-hand side operand if necessary.
$ perl -M5.010 -e'
sub f { say "f"; $ARGV[0] }
sub g { say "g"; $ARGV[1] }
say f() || g();
' 3 4
f
3
$ perl -M5.010 -e'
sub f { say "f"; $ARGV[0] }
sub g { say "g"; $ARGV[1] }
say f() || g();
' 0 4
f
g
4
This is why you can can evaluate open(...) or die(...)
safely. Without short-circuiting, it would evaluate die
whether open
was successful or not.
Now, let's explain the following:
$s = "X"; 1 ? $s .= "_" : $s = "_"; say $s; # _
It's a precedence problem. The above is equivalent to
$s = "X"; ( 1 ? ($s .= "_") : $s ) = "_"; say $s; # _
$s .= "_"
returns $s
, so the conditional operator returns $s
, so the string _
is assigned to $s
. If we add parens to get the desired parsing, we get the expected result.
$s = "X"; 1 ? ($s .= "_") : ($s = "_"); say $s; # X_
Alternative:
$s = "X"; $s = ( 1 ? $s : "" ) . "_"; say $s; # X_
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With