Ternary Operator

lua-users home
wiki

The Problem

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.

Standard solution: and/or

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.

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.

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

Anonymous functions/closures

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].

Functional-if

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

Boxing/unboxing

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

Stack

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

Syntax Extensions

The following are some proposals for extending Lua syntax to support the ternary operator more directly.

Simple syntax extension

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)

Syntax extension: then/or keywords

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

Syntax extension: Pythonic "x if y else z"

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 .

MetaLua

MetaLua includes an example ([ifexpr.mlua]) for adding this syntax:

local foo = if bar then 1 else 2

--DavidManura, et al.

See Also

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


RecentChanges · preferences
edit · history
Last edited December 19, 2012 6:15 am GMT (diff)