6.6 Conditional Operator
Conditional Operator Syntax
The ternary conditional operator is unique in that it takes three arguments and uses a mixed syntax. If a
is an expression of type int
and b
and c
are expressions that can be converted to one another (e.g., compared with ==
), then
a ? b : c
is an expression of the promoted type of b
and c
. The only promotion allowed in Stan is from integer to real; if one argument is of type int
and the other of type real
, the conditional expression as a whole is of type real
. In all other cases, the arguments have to be of the same underlying Stan type (i.e., constraints don’t count, only the shape) and the conditional expression is of that type.
Conditional Operator Precedence
The conditional operator is the most loosely binding operator, so its arguments rarely require parentheses for disambiguation. For example,
a > 0 || b < 0 ? c + d : e - f
is equivalent to the explicitly grouped version
(a > 0 || b < 0) ? (c + d) : (e - f)
The latter is easier to read even if the parentheses are not strictly necessary.
Conditional Operator Associativity
The conditional operator is right associative, so that
a ? b : c ? d : e
parses as if explicitly grouped as
a ? b : (c ? d : e)
Again, the explicitly grouped version is easier to read.
Conditional Operator Semantics
Stan’s conditional operator works very much like its C++ analogue. The first argument must be an expression denoting an integer. Typically this is a variable or a relation operator, as in the variable a
in the example above. Then there are two resulting arguments, the first being the result returned if the condition evaluates to true (i.e., non-zero) and the second if the condition evaluates to false (i.e., zero). In the example above, the value b
is returned if the condition evaluates to a non-zero value and c
is returned if the condition evaluates to zero.
Lazy Evaluation of Results
The key property of the conditional operator that makes it so useful in high-performance computing is that it only evaluates the returned subexpression, not the alternative expression. In other words, it is not like a typical function that evaluates its argument expressions eagerly in order to pass their values to the function. As usual, the saving is mostly in the derivatives that do not get computed rather than the unnecessary function evaluation itself.
Promotion to Parameter
If one return expression is a data value (an expression involving only constants and variables defined in the data or transformed data block), and the other is not, then the ternary operator will promote the data value to a parameter value. This can cause needless work calculating derivatives in some cases and be less efficient than a full if
-then
conditional statement. For example,
data {
real x[10];
...
parameters {
real z[10];
...
model {
y ~ normal(cond ? x : z, sigma);
...
would be more efficiently (if not more transparently) coded as
if (cond)
y ~ normal(x, sigma);
else
y ~ normal(z, sigma);
The conditional statement, like the conditional operator, only evaluates one of the result statements. In this case, the variable x
will not be promoted to a parameter and thus not cause any needless work to be carried out when propagating the chain rule during derivative calculations.