A “greedy” evaluation of boolean expressions means that every subexpression is evaluated and only after this the full boolean expression returns its value.
An evaluation is “lazy” when only the parts necessary to decide about truth or falsehood of the expression are evaluated.
In the expl3
language, one can build up complex boolean expressions using logical operators “and” &&
, “or” ||
or “not” !
together with predicates and parentheses.
Since the last update of expl3
, such boolean expressions, used as the first argument to \bool_if:nTF
, are subject to greedy evaluation for implementation reasons. In particular, every subexpression needs to return a boolean value.
However, expl3
also provides lazy evaluation with special functions.
An example where the difference is important is the issue with unicode-math
that came to the light with the update, see unicode-math failing to compile after update of TL 2017
The current code (to be fixed) in unicode-math
has
\bool_if:nTF { \tl_if_single_p:n {##1} && \token_if_cs_p:N ##1 }
{ \seq_put_right:Nn \l_@@_cmd_range_seq {##1} }
{ \seq_put_right:Nn \l_@@_char_range_seq {##1} }
The first subexpression returns true if the argument is a single token; with lazy evaluation, if this returns false, it's not necessary to examine the second part in order to conclude the condition is false. With the greedy evaluation, the second part is examined also when the first part returns false.
The object of the function is to distinguish whether the value given to the range
keyword in the options to \setmainfont
consists of a single token which is a control sequence. What happened was that range=`\+
didn't pass the test \tl_if_single_p:n
, so the second conditional was not attempted; but with greedy evaluation, the second test is performed nonetheless, resulting in an error, because `\+
is not valid input for \token_if_cs_p:N
.
The fix is to use lazy evaluation:
\bool_lazy_and:nnTF { \tl_if_single_p:n {##1} } { \token_if_cs_p:N ##1 }
{ \seq_put_right:Nn \l_@@_cmd_range_seq {##1} }
{ \seq_put_right:Nn \l_@@_char_range_seq {##1} }
which will do the same as before.
The “lazy” functions have lazy
in their name:
\bool_lazy_all:nTF
\bool_lazy_any:nTF
\bool_lazy_and:nnTF
\bool_lazy_or:nnTF
The first two want, as their first argument, any number of conditional expressions, each surrounded by braces, so
\bool_lazy_all:nTF { {<cond-1>} {<cond-2>} ... { <cond-n> } }
{ <true code> }
{ <false code> }
will follow the true branch if all conditions return true, but will stop evaluation as soon as one condition returns false; similarly
\bool_lazy_any:nTF { {<cond-1>} {<cond-2>} ... { <cond-n> } }
{ <true code> }
{ <false code> }
will follow the true branch if any of the conditions returns true, and will stop as soon as one does.
The other two receive two boolean expressions as their first argument and are streamlined versions for the most common cases.
The change obviously doesn't affect \bool_if:NTF
, because this just examines one conditional. However there was another change related to conditionals: something like
\bool_if:NTF \foo { <true> } { <false> }
followed the false branch when \foo
wasn't a conditional. This doesn't happen any longer (and it's the cause for the break in mhchem
).
At some point in time, the idea had been that \bool_if:nTF
would perform lazy evaluation, but this revealed difficult and unmanageable, so the lazy evaluation functions were introduced for just the simple cases of “and” and “or”. This happened well after the code of unicode-math
had been written.