三元運算子

lua-users home
wiki

問題

有時你可能會希望使用 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]。會稱為「三元」是因為有三個運算元:abc。例如,在 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

一個常用且極力推薦的解法是結合 andor 二元運算子,這可以近似實作三元運算子

x = a and b or c
x = a and b or c and d or e

請參閱程式設計書中 LuaExpressionsTutorial,以深入了解這些二元運算子的特殊屬性,讓它們可以這樣運作。

print('x is ' .. (x < 0 and 'negative' or 'non-negative'))  -- this works!

主要的注意事項是,如果 ac 算出真,而 bd 分別算出假,那麼此運算式將不會完全像三元運算子一樣運作。在此中,「算出假」表示該值會是 falsenil,而「算出真」表示不會算出假。在上方的第一行中,a and b or c 會被解釋為 (a and b) or c(因為 and 的優先權高於 or),而如果 a 算出真,那麼運算式會變成 b or c,如果 b 算出假,那麼運算式會變成 c(而不是你可能想要的 b)。

常地,就如同我們原始範例中的情況,三元運算子的第二運算元永遠無法計算為假,所以你可以自由地使用這種慣用語,但要小心注意事項。如果 b 計算為假,請變更 a,讓其計算為完全相反的值,因此互換 bc

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

你也可以將 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)

語法擴充:then/or 關鍵字

部分人建議使用類似以下這些語法

x = a then b else c
x = a then b or c

但如果在條件式陳述中使用這些語法,則會造成模稜兩可

if a() then b() else c() then d() end

語法擴充:Pythonic「x if y else z」

就如 John Backus 多年前所指出的,將條件作為條件式三元運算子的中間論點有一些語法優勢

x = a when a < b else b

如果 c 是第一個為真的條件,則 x 將獲得 a 的值。如果將 when 定義成 and 但互換論點(所以先評估其第一個論點),則可以使用目前的 Lua 語法來實現此目的。遺憾的是,這實作起來不如最初看起來的那麼容易,因為這表示必須擱置一個表達式的評估,直到後接表達式評估完畢。這是 Python 新條件式表達式 [2] 所執行的動作。--匿名

LuaList:2006-09/msg00608.html 中的類似討論。

MetaLua

MetaLua 在範例 ([ifexpr.mlua]) 中新增了此語法

local foo = if bar then 1 else 2

--DavidManura 等人

參見

- http://www.lualearners.org/tutorial?tut=74


最近的更改 · 喜好
編輯 · 歷史
最後編輯於 2014 年 6 月 13 日下午 7:18 GMT (diff)