我们不需要三元运算符
三元运算符是紧凑代码的必备元素,?:
感觉它在任何编程语言中都应有一席之地。我经常使用它;你也应该经常使用它。然而,我并不喜欢这个运算符本身。它感觉很复杂,而且不完整。虽然我已经停止了 Leaf 的开发,但我确实找到了一个更好的选择。
让我们更深入地了解这个三元运算符是什么,然后展示一种语言如何避免它。
这个三元运算符是什么?
这个运算符的名称引发了一些疑问。三元运算符,从典型的数学意义上,或者从解析器的角度来看,是一个接受三个参数的运算符。运算符是一个具有特殊语法的函数,而不是通用的调用语法foo(a,b,c)
。最常见的是二元运算符,我们随处可见它们。
//binary operators
x + y
x / y
x * y
还有一些我们常见的一元运算符。一元意味着它们只有一个参数。
//unary operators
-a
~bits
!cond
一元运算符和二元运算符有很多。但是我们有哪些三元运算符的例子呢?
//ternary operators
cond ? true_value : false_value
你是不是绞尽脑汁想着还有什么别的?据我所知,没有。这就是为什么我们最终称之为“三元运算符”,而不是使用像“条件运算符”这样的正式名称。编程语言中没有其他三元运算符。
链式运算符
我们先暂时搁置一下。有些运算符序列和组合可能看起来像三元运算符,但实际上不是。
例如,这个 Python 比较似乎涉及三个参数。
if a < b < c:
go()
这看起来类似于三元运算符。它必须考虑所有三个参数才能正确计算。
然而,深入挖掘一下,这更像是一种语法糖,而不是真正的三元运算符。它等价于以下内容。
once_b = b
if a < once_b and once_b < c:
go()
您可能会发现几个仅显示 的示例a < b and b < c
,但这些示例是不正确的。原始形式保证b
仅被计算一次。我使用once_b = b
来展示此保证;我假设在实践中,另一个内部特性可以实现这一点。
这个 python 构造可以扩展以包含更多值。
if a < b < c < d <e < e:
go()
它是一个链式运算符。可以添加的内容没有限制。
C++中的流运算符<<
也可以链式操作。
cout << "Hello " << first_name << " " << last_name << endl;
与 Python 的链式比较不同,此 C++ 序列不需要任何解析器支持。<<
是一个恰好可链式执行的普通二元运算符。例如,基础数学也具有同样的属性a + b + c + d
。
混乱的语法和解析
我说过编程语言中没有其他三元运算符。这是有原因的。
看看cond ? true_value : false_value
。有什么让它看起来像是涉及三个参数吗?如果我稍微将运算符更改为未知的运算符会怎么样:expr ⋇ value_a ⊸ value_b
。这看起来像两个二元运算符。即使我保留相同的符号,但添加嵌套表达式,它看起来也不对:cond ? value_a + value_b : value_c * value_d
。没有视觉迹象表明它? ... :
是一个统一的运算符。这种语法限制阻止了任何新的三元运算符的引入。这会引起强烈反对。?:
已经被一些程序员视为黑马,因为它很容易被滥用。
尽管三元运算符提供了有价值的语法,但它的语法却很混乱。用两个分割符号定义三个参数实在是太奇怪了。一个包含三个参数的函数没有问题,因为我们有健壮的语法foo( a, b, c )
。
优先级问题进一步增加了复杂性。所有运算符都需要优先级。例如,a + b * c
的计算结果为a + (b*c)
。乘法的优先级高于加法。乘法的结果先于加法计算。
相同优先级的运算符也具有结合性。例如,5 - 2 + 3
的计算结果为(5 - 2) + 3 = 6
,即左结合。 右结合的运算符的计算结果为5 - (2 + 3) = 0
,即错误。
右结合律通常只用于一元运算符(它只有一个右参数)和赋值运算符。例如,如果你疯狂地写了a = b += c
,那么右结合律会将其计算为a = (b += c)
。
三元运算符是右结合的。但这感觉不对。一个有三个参数和两个符号的运算符,怎么能仅仅将其结合性定义为左结合或右结合呢?
cond_a ? val_one : cond_b ? val_two : val_three
//right? associative
cond_a ? val_one : (cond_b ? val_two : val_three)
//left? associative (wrong)
(cond_a ? val_one : cond_b) ? val_two : val_three
//strict-left? associative (wacky)
(((cond_a ? val_one) : cond_b) ? val_two) : val_three
//strict-right? associative (nonsensical)
cond_a ? (val_one : (cond_b ? (val_two : val_three)))
两种严格形式对每个运算符号都应用了结合性,这很古怪。从解析器的角度思考一下。它是语法上最合理的两种形式之一。前两种形式需要一些技巧才能绕过常规的结合解析。
试想一下嵌套三元运算符会发生什么:a ? b ? c : d : e
。你不会经常在代码中看到嵌套三元运算符。它们太难在脑子里解析了。
你会发现很多代码都依赖于准右结合律。这正是链式操作得以实现的原因。
int a = cond_a ? val_one :
cond_b ? val_two :
cond_c ? val_three : val_four;
二元解决方案
当我在开发 Leaf 的时候,我想避免使用三元运算符。我喜欢它提供的功能,但我想找到一种更根本的方法来提供它。
解决方案以语言选项的形式出现。这些选项是 Leaf 的基础,因此有操作员支持。
第一个运算符是默认运算符。如果设置了可选值,则它的值为该值。否则,它的值为提供的默认值。
var a : optional
print( a | 1 ) //prints 1, since `a` has no value
var b : optional = 2
print( b | 3 ) //prints 2, since that's the value in `b`
JavaScript 中的运算符||
在很多情况下都能达到同样的效果。C# 中有一个空合并运算符,??
其使用null
方式与 Leaf 使用未设置可选值的方式相同。
这听起来像是三元运算符功能的一半。我们可以这样理解:cond ? some_val | default_val
。也就是说,考虑整个左侧部分,即 ,cond ? some_val
以生成一个可选值。给定一个可选值,我们已经有了运算符,可以在未设置时将该值设置为默认值。
Leaf 合并了?
运算符来创建一个可选项。
var a = true ? 5 //a is an optional set to value 5
var b = false ? 6 //b is an optional without a value
单独使用时,该运算符通常适用于可选值。与|
默认运算符结合使用时,它模拟了传统的三元运算符。
var a = cond ? true_value | false_value
int a = cond ? true_value : false_value;
?
和|
都是二元运算符。无需实际的三元运算符即可产生相同的条件求值。我们获得了相同的功能,却避免了语法上的混乱。发生的事情更加清晰,解析器也无需任何技巧。
由于这两个运算符本身都是有用的,因此它还可以提高表达能力而无需引入更多符号。
唉,我还没看到任何语言有计划添加这个功能。如果你知道的话,请留言。我很想看看。
鏂囩珷鏉ユ簮锛�https://dev.to/mortoray/we-dont-need-a-ternary-operator-309n