三元运算符

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

有关这些二元运算符的特殊属性(使它们能够以这种方式工作)的详细信息,请参阅 ProgrammingInLua ?ExpressionsTutorial

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

语法扩展:Python 式的“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 中的类似评论。

Lua

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 (差异)