Ternary Operator |
|
There are times when it would be preferable to use an if-then-else conditional statement as an expression. Consider this code:
if x < 0 then print('x is negative') else print('x is non-negative') end
Stylistically, repetition like print('x is ' ...)
is to be avoided ([DRY]), particularly if this repeated code were instead something much more complicated. One way to resolve that is with a variable:
local sign if x < 0 then sign = 'negative' else sign = 'non-negative' end print('x is ' .. sign)
But now we've introduced new style problems: although the naming of a value (sign
) can be useful for documentation purposes, the name is repeated in four locations, its scope undesirably extends beyond the print
statement, and the length and complexity of the code is arguably increased. What we may really want to do is bring the if-then-else into the expression, like this:
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)
where the form if a then b else c end
would be an expression that evaluates to b
when a
is true and otherwise evaluates to c
. However, this syntax is not supported in Lua. Some languages do support this construct directly: it is called a conditional ternary operator [1]. It is "ternary" because there are three operands: a
, b
, and c
. For example, in the C language, the ternary operator is written like
sign = (x < 0) ? "negative" : "non-negative";
Ternary operations can also be chained, analogous to "elseif
" clauses:
x = (a < amin) ? amin : (a > amax) ? amax : a;
Here, the ternary operator has right-associativity, which means the parenthesis are implied according to the first (not second) line below:
x = (a < amin) ? amin : ((a > amax) ? amax : a); x = ((a < amin) ? amin : (a > amax)) ? amax : a;
This is analogous to how the following Lua if-then-else statements are equivalent:
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
Even though Lua lacks ternary operators explicitly, there are ways to closely approximate it, as described below.
A frequently used and highly recommend solution is to combine the and
and or
binary operators in a way that closely approximates the ternary operator:
x = a and b or c x = a and b or c and d or e
See the book ProgrammingInLua? or ExpressionsTutorial for details on the special properties of these binary operators that allow them to work this way.
print('x is ' .. (x < 0 and 'negative' or 'non-negative')) -- this works!
The main caveat is that if a
or c
evaluates to true while b
or d
respectively evaluate to false, then this expression will not behave exactly like the ternary operator. Here, "evaluate to false" means that the value is either false
or nil
, and "evaluate to true" means not evaluate to false. In the first line above, a and b or c
is interpreted as (a and b) or c
(because and
has higher precedence than or
), and if a
evaluates to true, then the expression becomes b or c
, and if b
evaluates to false, then the expression becomes c
(not b
as you might want).
Often, as in the case of our original example, the second operand of the tertiary operator can never evaluate to false, so you are free to use this idiom, but beware of the caveat. If the b
will evaluate to false, change the a
so that it evaluates exactly opposite and therefore swaps b
and c
print((x < 0 and false or true)) -- this fails!
print((x >= 0 and true or false)) -- this works!
You can insert arbitrary statements inside expressions via an anonymous function (or closure), and this includes if-then-else statements:
print('x is ' .. (function() if x < 0 then return 'negative' else return 'non-negative' end end)())
A main downside is that this creates an anonymous closure on every execution, which may reduce performance in a tight loop. Also, anonymous function syntax is a bit verbose in Lua (as detailed in ShortAnonymousFunctions).
See also [ExpressionsAsStatements].
One can also write if
as a function:
function fif(condition, if_true, if_false) if condition then return if_true else return if_false end end print( fif(condition, a, b) )
but this does not have the advantage of short-circuiting unless the conditions are expressed as anonymous closures for delayed evaluation:
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
To avoid the above problems with nil's, you may "box" those values in some expression that is never nil. Unfortunately, the boxing imposes an overhead.
local condition, a, b = true, false, true local x = (condition and {a} or {b})[1] print(x) --> false
Here's a similar solution but using functions:
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
Here's an interesting (and rarely used) stack-like approach, with stack of size 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
The following are some proposals for extending Lua syntax to support the ternary operator more directly.
Perhaps the most Lua-ish syntax extension, introducing no new keywords, and preserving the current conditional syntax as much as possible, is something like these:
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
should be used here
x = (a then b else c)
, with parenthesis required
Some people propose syntaxes like these:
x = a then b else c x = a then b or c
but they cause ambiguities when used inside conditional statements:
if a() then b() else c() then d() end
There are syntactic advantages to having the condition as the middle argument of the conditional ternary operator, as John Backus pointed out many years ago:
x = a when a < b else b
gives x
the value of a
if c
is the first condition to be true. This could be accomplished with the current Lua syntax if when
were defined as and
but with arguments interchanged (so with lazy evaluation of its first argument). Unfortunately this is not as straightforward to implement as might at first appear, because it means holding over the evaluation of an expression until a succeeding expression has been evaluated. This is what was done for Python's new conditional expressions [2]. --Anonymous
See also similar comments in LuaList:2006-09/msg00608.html .
LuaMetaLua includes an example ([ifexpr.mlua]) for adding this syntax:
local foo = if bar then 1 else 2
--DavidManura, et al.
- http://www.lualearners.org/tutorial?tut=74