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.

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!

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 June 13, 2014 6:18 pm GMT (diff)