三元运算符

嗯,是的,很多编程语言是支持一种特定的三元运算符(Ternary Operator)的,不过我先不打算用代码的方式来解释这个运算符。我们先以代数的方式来介绍这种运算符。(如果您已经了解什么是三元运算符,请大胆第前往下一个章节)

从代数上来说,我们可以把一个N元运算符(算子)定义为一个N元函数的形式,那么我们假定这个三元运算符叫做\(\Xi_3\),那么实际上,这个三元算子可以被表述为这样一个函数:

\[\Xi_3 \left( o_1,o_2,o_3 \right) =\begin{cases}o_2 &,\mathop{\bf{1}} \left( o_1 \right)=1 \\o_3 &,\mathop{\bf{1}} \left( o_1 \right)=0 \\\end{cases}\]

这里面的\(o_1\)\(o_3\)就是三个运算元,\(\mathop{\bf{1}} (x)\)叫做逻辑幺函数,这个函数采取任意形式的\(o_1\),若\(o_1\)能被解释为\(F\)(逻辑0,逻辑矛盾式)则该函数都输出\(0\),否则总输出\(1\)

换言之,上面的三元运算\(\Xi_3\)就表示了这样的含义:

通过这样一种运算符可以进行一个很便利的条件选择,很多程序语言中也都提供了这样的运算符,考虑到我们写程序的代码是线性排版的(排在一行里),因此如果不使用函数而是使用运算符构成中缀表达式挤在运算元中间时,我们会发现:

op1 _ op2 _ op3

是的,与二元运算符不同,使用运算符区分三个运算元时需要至少两个字符,放在两个空挡处,因此很多程序语言提供给的是这个运算符?:,也就是:

op1 ? op2 : op3

用起来非常优雅简洁,可以让我们节省大量的代码行数,少些若干肥肥的if语句,尽管大量嵌套的话可读性会下降,不过尽量避免这一点就好。

Lua中的三元运算符

非常遗憾,翻遍整个Lua的参考文档,Lua并没有提供这个东西……

就在听过这个令人沮丧的消息后,我无意中看到了一个这样的解决方法,可以说骚断了我的腰……

(a and {b} or {c})[1]

这种方案使用了一个and和一个or运算符,号称完成了三元运算符的功能,起初我8太相信,但是看到Lua里关于逻辑运算的描述,我终于看懂了……

为什么会这样

Lua可以说是一个步伐六亲不认,不走寻常路的鬼才语言,虽然目前官网上一片死寂。

其中一个不寻常就是,Lua里只有nilfalse可以被解释为逻辑false,其余包括0[[]](空字符串)在内的所有内容全是true

而第二个不寻常的玩法就是,Lua的逻辑运算符andor并不一定返回truefalse,它的返回值满足某种吸收原则,这种吸收原则用一句话表示就是:

分解到这两个运算符身上就是:

  1. 对于and运算符,表达式a and b会在a解释为false时返回a(左吸收),否则返回b(右吸收)
  2. 对于or运算符,表达式a or b会在a解释为true时返回a(左吸收),否则返回b(右吸收)

这样一来我们回到这个情形:

op1 _1 op2 _2 op3

我们当然是希望op1被解释为true时得到op2,否则得到op3。那我们就具体考虑一下当op1被解释为true时应当怎样,要返回op2,则对于前部op1 _1 op2而言需要发生右吸收,对照上面的吸收规则,那么_1就应当是and。依然考虑op1true,第一次吸收后表达式变成了op2 _2 op3,此时我们希望左吸收,不过这里有个问题,op2被解释为truefalse又是两种情况,我们先考虑op2解释为true的情况,此时要完成左吸收,则个根据吸收规则,_2应当使用or运算符,于是整个表达式变成了op1 and op2 or op3,这个表达式可以解决绝大部分情况。

但是,就如前面所担忧的,这种做法并不能处理op1 and false or op3的情况,因为op1 and false部分会被恒定地置为false,而左falseor运算符无法进行左吸收(因为不能短路求值),这种情况下无论op1是多少都只能返回op3

既然如此,那么我们就需要对op2op3进行包装,Lua第三个不寻常的地方就是那个妖娆的tabletable简直就是个万金油数据结构,什么都能往里塞,而且无论装不装东西,table总能被解释为true,这就不会引发op1 and op2总返回false的情况,从而避免了or的右吸收,于是我们就考虑把op2op3用两个table分别包装起来。

a and {b} or {c}

然而我们希望返回的结果是表里的元素,而非这张表,因此我们取下元素:

(a and {b} or {c})[1] --Lua里下标从1开始

大功告成!

后记

07-19 10:16