10

Consider the following example:

\documentclass{article}

\makeatletter
\newcommand\fail[1]{%
    \ifnum1=#1% <- here a space is missing
        \expandafter \@firstoftwo
    \else
        \expandafter \@secondoftwo
    \fi
}
\makeatother

\begin{document}
    \fail{1}{true}{false}
\end{document}

With 1=1 it prints false, with 1=2 it prints truefalse.

I know that this is a problem of \ifnum expanding too much hoping to find a continuation of the number and that this can be fixed by inserting a space after #1.

However, I do not understand what exactly is happening.

This answer seems related but I am still unable to explain what is going on.

1 Answer 1

10

TL;DR

Never forget a space after a constant, unless you know precisely why you omit it (the \romannumeral trick is a prime example of why one might want to omit a space after a constant).

What happens?

What happens is that, due to the lack of a terminator for the number being evaluated, \expandafter is expanded; this touches \else, so, by rule, TeX inserts \relax (the “frozen” type) in front of \else (note that \expandafter has already gone). See Normal \relax vs. frozen \relax.

Thus you get the same as

\ifnum1=1\@firstoftwo\relax\else\expandafter\@secondoftwo\fi{true}{false}

Now expansion resumes, because the number is still unfinished and \@firstoftwo returns \relax; the test is then evaluated to true so TeX remains with

\relax\expandafter\@secondoftwo\fi{true}{false}

The \relax token is executed (or stored in the case of \edef) and \else is gobbled up and we get

《\relax》\expandafter\@secondoftwo\fi{true}{false}

Now \expandafter removes \fi and you end up with

《\relax》\@secondoftwo{true}{false}

so finally \relax false. If you do

\edef\test{\fail{1}{true}{false}}\show\test

the terminal will indeed show

> \test=macro:
->\relax false.

If you add \tracingmacros=1 before the call to \fail, the log file will show

\fail #1->\ifnum 1=#1\expandafter \@firstoftwo \else \expandafter \@secondoftwo
 \fi 
#1<-1

\@firstoftwo #1#2->#1
#1<-\relax 
#2<-\else 

\@secondoftwo #1#2->#2
#1<-true
#2<-false

confirming my analysis.

With 《...》 I denote tokens that have already been sent to the further level.

If the call is \fail{2}{true}{false}, the first step yields

\ifnum1=2\@firstoftwo\relax\else\expandafter\@secondoftwo\fi{true}{false}

Since the number is not yet finished, \@firstoftwo is expanded, returning \relax:

\ifnum1=2\relax\expandafter\@secondoftwo\fi{true}{false}

The test now can be evaluated to false, so everything up to \fi (as \else no longer is present) is removed without expansion, leading to

\fi{true}{false}

and \fi now disappears. Indeed, \edef\test{\fail{2}{true}{false}}\show\test prints

> \test=macro:
->{true}{false}.

on the terminal.

What about if you exchange \@firstoftwo and \@secondoftwo?

\documentclass{article}

\makeatletter
\newcommand\fail[1]{%
    \ifnum1=#1% <- here a space is missing
        \expandafter \@secondoftwo
    \else
        \expandafter \@firstoftwo
    \fi
}
\makeatother

\begin{document}

\edef\test{\fail{1}{false}{true}}\show\test

\tracingmacros=1 \tracingonline=1

    \fail{1}{false}{true}

\tracingmacros=0

\end{document}

Now the first level expansion will again add \relax, but we get

\ifnum1=1\@secondoftwo\relax\else\expandafter\@firstoftwo\fi{false}{true}

As before, \@secondoftwo is expanded, returning \else. Oh, but the number is yet unfinished, so a further frozen \relax is added, to yield

\ifnum1=1\relax\else\expandafter\@firstoftwo\fi{false}{true}

Now the conditional is evaluated to true, and the test is removed:

\relax\else\expandafter\@firstoftwo\fi{false}{true}

As before, \relax is sent down to the stomach and \else removes all tokens up to the matching \fi, without expansion:

《\relax》\fi{false}{true}

The \fi disappears and we remain with the equivalent of

\relax{false}{true}
2
  • now I see. And in the other case, with 1=2, \@firstoftwo gobbles the \else in the same way so that TeX discards everything until \fi when reading the \relax and evaluating \ifnum. Therefore the two groups {true}{false} remain untouched and are printed to the pdf. Thank you.
    – jakun
    Jun 12, 2019 at 12:46
  • 1
    @jakun I added some further analysis and fixed a weakness in the description.
    – egreg
    Jun 12, 2019 at 12:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .