How to define a macro inside a macro via \if?
In your second example, when the \ifnum
takes the first branch, TeX executes \def\foo\else...
, which is quite problematic as we'll explain below. Things are similar when the other branch is taken: \def\bar\fi...
.
What happens precisely
Let's take for instance the case where the \ifnum
test is true. In this case, \foo
gets defined, but not the way you expected. The actual definition is:
\def\foo\else\def\bar\fi{hello}
This is a valid macro definition where the parameter text is \else\def\bar\fi
, and the replacement text is hello
. So, whenever \foo
is expanded after this definition, TeX wants to see the tokens \else\def\bar\fi
right afterwards. But in your example, the token after \foo
's call is a control space \
, therefore you get the error:
./faulty.tex:11: Use of \foo doesn't match its definition.
l.11 \foo\
\bar
Runaway argument?
The other case defines \bar
as:
\def\bar\fi{world}
which, when expanded towards the end of the TeX run, fails for the same reason (\bye
isn't the same token as the expected \fi
).
Remedy
A simple way to fix that is to expand the \else
or \fi
at the appropriate time, in order to remove the unwanted tokens from the input stream before TeX gets to execute the \def
(i.e., before the \def
from the chosen branch reaches TeX's stomach):
\count255=0
\def\newdef{\advance\count255 by1
\ifnum\count255=1
\expandafter\def\expandafter\foo
\else
\expandafter\def\expandafter\bar
\fi
}
\newdef{hello}
\newdef{world}
\foo\ \bar
\bye
This is essentially the same trick as the classical one using LaTeX's \@firstoftwo
and \@secondoftwo
.
Expanding the \else
when the condition is true removes all tokens between the \else
and the matching \fi
(both inclusive; note that if the condition were still undecided, a frozen \relax
would be inserted). Expanding the \fi
simply removes it. So, in the first case, what is left in the input stream is:
\def\foo{hello}
\newdef{world}
\foo\ \bar
\bye
and in the second case:
\def\bar{world}
\foo\ \bar
\bye
Going a little further
I said above that if you try to expand the \else
token when the condition is still undecided, a frozen \relax
is inserted. In this case, making this become reality only requires adding a percent sign after \ifnum\count255=1
, because then TeX would continue expanding tokens after the 1
(the 〈number〉 following the =
sign wouldn't be finished yet) and would therefore expand the two \expandafter
s before this 〈number〉 has been fully read. So, removing the end part of the code that doesn't matter here, the test code could be:
\count255=0
\def\newdef{\advance\count255 by1
\ifnum\count255=1% bug here: the second <number> isn't finished!
\expandafter\def\expandafter\foo
\else
\expandafter\def\expandafter\bar
\fi
}
\newdef{hello}\show\foo
\bye
which shows the inserted frozen \relax
inside \foo
's definition:
> \foo=macro:
\relax \else \expandafter \def \expandafter \bar \fi ->hello.
l.9 \newdef{hello}\show\foo
The problem is, as described in Frougon's very fine answer, that conditionals in TeX work in quite a peculiar way: when the test is evaluated true, \else...\fi
will only disappear as a result of expanding \else
, which removes everything up to the matching \fi
(without expansion). When the test is evaluated false, everything up to \else
(or \fi
) will be removed (without expansion). A dangling \fi
will disappear anyway, because its expansion is void.
The method with \csname
works, because expansion is performed all the way until only character tokens remain (other unexpandable tokens will throw a Missing \endcsname
error), so \else
and \fi
will disappear as described above.
Workaround? Since you're doing definitions, expandability is not a concern and you can use Knuth's preferred construction with tail recursion and \next
.
\count255=0
\def\newdef{\advance\count255 by1
\ifnum\count255=1
\def\next{\def\foo}%
\else
\def\next{\def\bar}%
\fi
\next
}
\newdef{hello}
\newdef{world}
\foo\ \bar
\bye