我们不需要三元运算符

2025-06-10

我们不需要三元运算符

三元运算符是紧凑代码的必备元素,?:感觉它在任何编程语言中都应有一席之地。我经常使用它;你也应该经常使用它。然而,我并不喜欢这个运算符本身。它感觉很复杂,而且不完整。虽然我已经停止了 Leaf 的开发,但我确实找到了一个更好的选择。

让我们更深入地了解这个三元运算符是什么,然后展示一种语言如何避免它。

这个三元运算符是什么?

这个运算符的名称引发了一些疑问。三元运算符,从典型的数学意义上,或者从解析器的角度来看,是一个接受三个参数的运算符。运算符是一个具有特殊语法的函数,而不是通用的调用语法foo(a,b,c)。最常见的是二元运算符,我们随处可见它们。

//binary operators
x + y
x / y
x * y
Enter fullscreen mode Exit fullscreen mode

还有一些我们常见的一元运算符。一元意味着它们只有一个参数。

//unary operators
-a
~bits
!cond
Enter fullscreen mode Exit fullscreen mode

一元运算符和二元运算符有很多。但是我们有哪些三元运算符的例子呢?

//ternary operators
cond ? true_value : false_value
Enter fullscreen mode Exit fullscreen mode

你是不是绞尽脑汁想着还有什么别的?据我所知,没有。这就是为什么我们最终称之为“三元运算符”,而不是使用像“条件运算符”这样的正式名称。编程语言中没有其他三元运算符。

链式运算符

我们先暂时搁置一下。有些运算符序列和组合可能看起来像三元运算符,但实际上不是。

例如,这个 Python 比较似乎涉及三个参数。

if a < b < c:
    go()
Enter fullscreen mode Exit fullscreen mode

这看起来类似于三元运算符。它必须考虑所有三个参数才能正确计算。

然而,深入挖掘一下,这更像是一种语法糖,而不是真正的三元运算符。它等价于以下内容。

once_b = b
if a < once_b and once_b < c:
    go()
Enter fullscreen mode Exit fullscreen mode

您可能会发现几个仅显示 的示例a < b and b < c,但这些示例是不正确的。原始形式保证b仅被计算一次。我使用once_b = b来展示此保证;我假设在实践中,另一个内部特性可以实现这一点。

这个 python 构造可以扩展以包含更多值。

if a < b < c < d <e < e:
    go()
Enter fullscreen mode Exit fullscreen mode

它是一个链式运算符。可以添加的内容没有限制。

C++中的流运算符<<也可以链式操作。

cout << "Hello " << first_name << " " << last_name << endl;
Enter fullscreen mode Exit fullscreen mode

与 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)))
Enter fullscreen mode Exit fullscreen mode

两种严格形式对每个运算符号都应用了结合性,这很古怪。从解析器的角度思考一下。它是语法上最合理的两种形式之一。前两种形式需要一些技巧才能绕过常规的结合解析。

试想一下嵌套三元运算符会发生什么:a ? b ? c : d : e。你不会经常在代码中看到嵌套三元运算符。它们太难在脑子里解析了。

你会发现很多代码都依赖于准右结合律。这正是链式操作得以实现的原因。

int a = cond_a ? val_one :
    cond_b ? val_two :
    cond_c ? val_three : val_four;
Enter fullscreen mode Exit fullscreen mode

二元解决方案

当我在开发 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`
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

单独使用时,该运算符通常适用于可选值。与|默认运算符结合使用时,它模拟了传统的三元运算符。

var a = cond ? true_value | false_value
Enter fullscreen mode Exit fullscreen mode
int a = cond ? true_value : false_value;
Enter fullscreen mode Exit fullscreen mode

?|都是二元运算符。无需实际的三元运算符即可产生相同的条件求值。我们获得了相同的功能,却避免了语法上的混乱。发生的事情更加清晰,解析器也无需任何技巧。

由于这两个运算符本身都是有用的,因此它还可以提高表达能力而无需引入更多符号。

唉,我还没看到任何语言有计划添加这个功能。如果你知道的话,请留言。我很想看看。

鏂囩珷鏉ユ簮锛�https://dev.to/mortoray/we-dont-need-a-ternary-operator-309n
PREV
你想写什么代码来放松一下?添加应用程序“页面”模型 #2657
NEXT
面向内容创作者的互联网彩票™