三元運算子 |
|
有時你可能會希望使用 if-then-else 條件式作為運算式。考慮以下程式碼
if x < 0 then print('x is negative') else print('x is non-negative') end
從風格來看,像是 print('x is ' ...)
的重複應該避免([DRY]),特別是如果重複的程式碼比較複雜的話。解決此問題的方法之一是使用變數
local sign if x < 0 then sign = 'negative' else sign = 'non-negative' end print('x is ' .. sign)
不過現在我們引入了新的風格問題:雖然命名值(sign
)有助於撰寫文件,但這個名稱重複出現四次,其範圍不必要地延伸到 print
敘述之外,而程式的長度和複雜性顯然增加了。我們真正想要的是將 if-then-else 帶入運算式中,如下所示
local sign = if x < 0 then 'negative' else 'non-negative' end print('x is ' .. sign) -- or just this... print('x is ' .. if x < 0 then 'negative' else 'non-negative' end)
在這裡,if a then b else c end
這個形式會是一個表達式,當 a
為真時會算出 b
,否則會算出 c
。不過 Lua 不支援這種語法。有些程式語言直接支援這種結構:它稱為三元條件運算子 [1]。會稱為「三元」是因為有三個運算元:a
、b
和 c
。例如,在 C 語言中,三元運算子寫成
sign = (x < 0) ? "negative" : "non-negative";
三元運算也可以串聯,類似於「elseif
」子句
x = (a < amin) ? amin : (a > amax) ? amax : a;
在這裡,三元運算子具有右結合性,這表示括弧會根據以下第一行(而非第二行)含義來暗示
x = (a < amin) ? amin : ((a > amax) ? amax : a); x = ((a < amin) ? amin : (a > amax)) ? amax : a;
這類似於下列 Lua if-then-else 陳述式的等價關係
if a < amin then x = amin elseif a > amax then x = amax else x = a end
if a < amin then x = amin else if a > amax then x = amax else x = a end end
儘管 Lua 明確缺少三元運算子,但有辦法可以近似它,如下所述。
一個常用且極力推薦的解法是結合 and
和 or
二元運算子,這可以近似實作三元運算子
x = a and b or c x = a and b or c and d or e
請參閱程式設計書中 Lua?或ExpressionsTutorial,以深入了解這些二元運算子的特殊屬性,讓它們可以這樣運作。
print('x is ' .. (x < 0 and 'negative' or 'non-negative')) -- this works!
主要的注意事項是,如果 a
或 c
算出真,而 b
或 d
分別算出假,那麼此運算式將不會完全像三元運算子一樣運作。在此中,「算出假」表示該值會是 false
或 nil
,而「算出真」表示不會算出假。在上方的第一行中,a and b or c
會被解釋為 (a and b) or c
(因為 and
的優先權高於 or
),而如果 a
算出真,那麼運算式會變成 b or c
,如果 b
算出假,那麼運算式會變成 c
(而不是你可能想要的 b
)。
常地,就如同我們原始範例中的情況,三元運算子的第二運算元永遠無法計算為假,所以你可以自由地使用這種慣用語,但要小心注意事項。如果 b
計算為假,請變更 a
,讓其計算為完全相反的值,因此互換 b
和 c
print((x < 0 and false or true)) -- this fails!
print((x >= 0 and true or false)) -- this works!
你可以透過匿名函式(或閉包)在表達式內插入任意外部程式,其中包括 if-then-else 陳述式
print('x is ' .. (function() if x < 0 then return 'negative' else return 'non-negative' end end)())
主要的缺點是在每次執行時都會建立一個匿名閉包,這可能會降低封閉迴圈的效能。此外,在 Lua 中,匿名函式的語法有點冗長(詳情請參見 ShortAnonymousFunctions)。
另請參閱 [ExpressionsAsStatements]。
你也可以將 if
寫為一個函式
function fif(condition, if_true, if_false) if condition then return if_true else return if_false end end print( fif(condition, a, b) )
但這不會有短路運算的優點,除非條件表達為延遲評估的匿名閉包
function fif(condition, if_true, if_false) if condition then return if_true() else return if_false() end end local x = fif(condition, function() return a end, function() return b end) print(x) --> false
若要避免上述 nil 的問題,你可以將那些值「裝箱」在不含 nil 的特定表達式中。遺憾的是,裝箱會造成負擔。
local condition, a, b = true, false, true local x = (condition and {a} or {b})[1] print(x) --> false
以下是一個類似的解決方案,但使用函式
local False = {} local Nil = {} local function bwrap(o) return o == nil and Nil or o == false and False or o end local function bunwrap(o) if o == Nil then return nil elseif o == False then return false else return o end end local x = bunwrap(condition and bwrap(a) or b) print(x) --> false
以下是一個有趣(而且很少使用)的類似堆疊的作法,堆疊大小為 1
local save, restore do local o_saved save = function(o) o_saved = o; return true end restore = function() return o_saved end end local x = (condition and save(a) or save(b)) and restore() print(x) --> false
以下是部分提議,以直接擴充 Lua 語法來支援三元運算子。
或許最符合 Lua 精神的語法擴充,不引入任何新關鍵字,並在最大可能範圍內保留目前的條件式語法,類似以下這些方式
x = if a then b elseif c then d else e end x = (if a then b elseif c then d else e end) x = (a then b elseif c then d else e)
end
的評論x = (a then b else c)
的論點,需要使用括號
部分人建議使用類似以下這些語法
x = a then b else c x = a then b or c
但如果在條件式陳述中使用這些語法,則會造成模稜兩可
if a() then b() else c() then d() end
就如 John Backus 多年前所指出的,將條件作為條件式三元運算子的中間論點有一些語法優勢
x = a when a < b else b
如果 c
是第一個為真的條件,則 x
將獲得 a
的值。如果將 when
定義成 and
但互換論點(所以先評估其第一個論點),則可以使用目前的 Lua 語法來實現此目的。遺憾的是,這實作起來不如最初看起來的那麼容易,因為這表示必須擱置一個表達式的評估,直到後接表達式評估完畢。這是 Python 新條件式表達式 [2] 所執行的動作。--匿名
見 LuaList:2006-09/msg00608.html 中的類似討論。
LuaMetaLua 在範例 ([ifexpr.mlua]) 中新增了此語法
local foo = if bar then 1 else 2
--DavidManura 等人
- http://www.lualearners.org/tutorial?tut=74