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
c are expressions that can be
converted to one another (e.g., compared with
a ? b : c
is an expression of the promoted type of
only promotion allowed in Stan is from integer to real; if one
argument is of type
int and the other of type
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
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
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
then conditional statement. For example,
y ~ normal(cond ? x : z, sigma);
would be more efficiently (if not more transparently) coded as
y ~ normal(x, sigma);
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