Lua騷操作——三元條件運算子

Oberon發表於2020-07-18

本文地址:https://www.cnblogs.com/oberon-zjt0806/p/13337577.html
本文參考了這篇文章

三元運算子

(如果您已經瞭解什麼是三元運算子,請大膽第前往下一個章節)

我知道有一元運算子(邏輯非,位反轉,負號),二元運算子(加減乘除等),這三元運算子是?

嗯,是的,很多程式語言是支援一種特定的三元運算子(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\)就表示了這樣的含義:

\(o_1\)不能被解釋為邏輯0,則\(\Xi_3\)返回\(o_2\),否則\(\Xi_3\)返回\(o_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開始

大功告成!

後記

……

……

我還能說什麼呢??太™馬叉蟲了!!!

相關文章