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}