Perl 条件(三元)运算符不进行快捷评估
Perl conditional (ternary) operator does no shortcut evaluation
The conditional (ternary) operator 表示三元运算符可替代 if
... else
。
我一直这么认为,但最近我有一个逻辑问题。
考虑这个简短的调试会话:
DB<1> $s='X'
DB<2> 1 ? $s .= '_' : $s = '_'
DB<3> x $s
0 '_'
因此,如果 1
为真,则应计算表达式 $s .= '_'
(而不是 $s = '_'
)。
但为什么 $s
最后是 '_'
?
条件运算符仅评估必要的内容,但您遇到了优先级问题。
首先,条件运算符确实保证使用 short-circuit evaluation,这意味着它只评估必要的内容。
$ 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
甚至 E1 || E2
、E1 or E2
、E1 && E2
和 E1 and E2
也是如此。他们仅在必要时评估其右侧操作数。
$ 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
这就是您可以安全地评估 open(...) or die(...)
的原因。如果不短路,它将评估 die
是否 open
成功。
现在,让我们解释一下:
$s = "X"; 1 ? $s .= "_" : $s = "_"; say $s; # _
这是一个优先级问题。以上相当于
$s = "X"; ( 1 ? ($s .= "_") : $s ) = "_"; say $s; # _
$s .= "_"
returns$s
,所以条件运算符returns$s
,所以字符串_
赋值给$s
.如果我们添加括号以获得所需的解析,我们将获得预期的结果。
$s = "X"; 1 ? ($s .= "_") : ($s = "_"); say $s; # X_
选择:
$s = "X"; $s = ( 1 ? $s : "" ) . "_"; say $s; # X_
这是运算符优先级的问题。
$ 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
无论三元条件为真还是假,最终$s
设置为"_"
。
要完成您打算做的事情,您至少需要添加一组括号:
1 ? $s .= '_' : ($s = '_');
三元条件运算符(?:
)的优先级高于赋值运算符(=
)(Perl运算符优先级的table可以在Operator Precedence and Associativity perlop 部分)。因此,行
1 ? $s .= '_' : $s = '_'
被 Perl 解析为
(1 ? ($s .= '_') : $s) = '_'
(你可以自己查运行 perl -MO=Deparse <your program>
)
另请注意,$s .= '_'
returns $s
最后添加了 _
,并且此 $s
可以分配给(在技术上术语,它是一个左值)。这记录在 perlop 的 Assignment Operators 部分:
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.
所以,基本上,您的代码正在执行
($s .= '_') = '_';
相当于
$s .= '_';
$s = '_';
The conditional (ternary) operator 表示三元运算符可替代 if
... else
。
我一直这么认为,但最近我有一个逻辑问题。
考虑这个简短的调试会话:
DB<1> $s='X'
DB<2> 1 ? $s .= '_' : $s = '_'
DB<3> x $s
0 '_'
因此,如果 1
为真,则应计算表达式 $s .= '_'
(而不是 $s = '_'
)。
但为什么 $s
最后是 '_'
?
条件运算符仅评估必要的内容,但您遇到了优先级问题。
首先,条件运算符确实保证使用 short-circuit evaluation,这意味着它只评估必要的内容。
$ 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
甚至 E1 || E2
、E1 or E2
、E1 && E2
和 E1 and E2
也是如此。他们仅在必要时评估其右侧操作数。
$ 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
这就是您可以安全地评估 open(...) or die(...)
的原因。如果不短路,它将评估 die
是否 open
成功。
现在,让我们解释一下:
$s = "X"; 1 ? $s .= "_" : $s = "_"; say $s; # _
这是一个优先级问题。以上相当于
$s = "X"; ( 1 ? ($s .= "_") : $s ) = "_"; say $s; # _
$s .= "_"
returns$s
,所以条件运算符returns$s
,所以字符串_
赋值给$s
.如果我们添加括号以获得所需的解析,我们将获得预期的结果。
$s = "X"; 1 ? ($s .= "_") : ($s = "_"); say $s; # X_
选择:
$s = "X"; $s = ( 1 ? $s : "" ) . "_"; say $s; # X_
这是运算符优先级的问题。
$ 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
无论三元条件为真还是假,最终$s
设置为"_"
。
要完成您打算做的事情,您至少需要添加一组括号:
1 ? $s .= '_' : ($s = '_');
三元条件运算符(?:
)的优先级高于赋值运算符(=
)(Perl运算符优先级的table可以在Operator Precedence and Associativity perlop 部分)。因此,行
1 ? $s .= '_' : $s = '_'
被 Perl 解析为
(1 ? ($s .= '_') : $s) = '_'
(你可以自己查运行 perl -MO=Deparse <your program>
)
另请注意,$s .= '_'
returns $s
最后添加了 _
,并且此 $s
可以分配给(在技术上术语,它是一个左值)。这记录在 perlop 的 Assignment Operators 部分:
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.
所以,基本上,您的代码正在执行
($s .= '_') = '_';
相当于
$s .= '_';
$s = '_';