3

Please consider the following MWE, which is an excerpt from a much larger project.

The scenario is to define a conditional \IfComp{<number>}{<defined>}{<undefined>} that queries, whether or not a final control sequence (in this case \csname test-<number>\endcsname) is defined via an intermediary control sequence (\csname macro-<number>\endcsname).

\documentclass{minimal}
\makeatletter
\parindent\z@
\expandafter\def\csname test-1\endcsname{foo}

\expandafter\def\csname macro-1\endcsname{\csname test-1\endcsname} \expandafter\def\csname macro-2\endcsname{\csname test-2\endcsname}

\def\testA{% \long\def\IfComp##1##2##3{% \expandafter\expandafter\expandafter\let\expandafter\csname @comp@name\expandafter\endcsname\csname macro-##1\endcsname% \expandafter\expandafter\expandafter\ifx@comp@name\relax ##3 \else ##2 \fi% }}

\def\testB{% \long\def\IfComp##1##2##3{% \expandafter\expandafter\expandafter\ifx\csname macro-##1\endcsname\relax ##3 \else ##2 \fi }}

\def\testC{% \long\def\IfComp##1##2##3{% %%% Why seven?!? \expandafter\expandafter\expandafter \expandafter\expandafter\expandafter \expandafter\ifx\csname macro-##1\endcsname\relax ##3 \else ##2 \fi }}

\begin{document} {\testA \textbf{TEST A}\ \IfComp{1}{defined}{undefined}\ \IfComp{2}{defined}{undefined}\

\textbf{TEST B}\ \protected@edef\x{\IfComp{1}{defined}{undefined}}\expandafter\verb\expandafter|\meaning\x| \ \protected@edef\x{\IfComp{2}{defined}{undefined}}\expandafter\verb\expandafter|\meaning\x| \ }

{\testB \textbf{TEST C}\ \IfComp{1}{defined}{undefined}\ \IfComp{2}{defined}{undefined}\

\textbf{TEST D}\ \protected@edef\x{\IfComp{1}{defined}{undefined}}\expandafter\verb\expandafter|\meaning\x| \ \protected@edef\x{\IfComp{2}{defined}{undefined}}\expandafter\verb\expandafter|\meaning\x| \ }

{\testC \textbf{TEST E}\ \IfComp{1}{defined}{undefined}\ \IfComp{2}{defined}{undefined}\

\textbf{TEST F}\ \protected@edef\x{\IfComp{1}{defined}{undefined}}\expandafter\verb\expandafter|\meaning\x| \ \protected@edef\x{\IfComp{2}{defined}{undefined}}\expandafter\verb\expandafter|\meaning\x| \ } \end{document}

which yields the following output:

Render Result of the code above

\TestA was my first attempt, and it works fine for most cases (cf. TEST A). However, I then came across a situation where I needed to put the whole query inside an \protected@edef{…} and the code crashed, since it leaves the \let\@comp@name\test-1 unexpanded in the stack (cf. TEST B).

So, I played a bit around, and came up with \TestB, hoping that \csname macro-##1\endcsname would expand twice before it is checked against \relax. But that doesn't work, I get "defined" for both cases (see TEST C and D).

After successively adding moar \expandafters, I eventually got it to work with 7 \expandafters (cf. TEST E and F).

My question is: Why do seven \expandafters work in both cases?

Lupino
  • 2,772
  • 1
    as to why 7 you almost always need 2^n-1 – David Carlisle Mar 06 '24 at 16:08
  • 1
    https://tex.stackexchange.com/questions/231996/expandafter-and-aftergroup-where-do-the-2n1-and-n2-1-rules-come-from/232003#232003 – David Carlisle Mar 06 '24 at 16:10
  • If you need to do lots of expansion, this question may be relevant: https://tex.stackexchange.com/questions/8076/can-one-define-a-superexpandaftern-that-would-expand-to-2n-1-expandafter – Steven B. Segletes Mar 06 '24 at 17:09
  • @StevenB.Segletes it's easier just to change \foo:n to \foo:e and automatically expand the argument with as many steps as needed:-: – David Carlisle Mar 06 '24 at 17:44
  • Thanks to David's answer, I understand now what my mental failure was: I thought that I needed to expand the \csname after the \ifx twice, but I now see that I need to expand it actually tree times. Now, the 7 \expandafter do make sense to me. – Lupino Mar 06 '24 at 19:56

1 Answers1

6

You almost always need 2^n -1 \expandafter in nested chains.

    %%% Why seven?!?

%1 %2 \expandafter\expandafter\expandafter %3 \expandafter\expandafter\expandafter %4 \expandafter\ifx %5 \csname macro-##1\endcsname\relax

if #1 is foo the above marks the first five expansion steps so the input buffer will then contain

%1
 \expandafter
            %2
 \expandafter\expandafter
\ifx
%3
 \macro-foo\relax

now \macro-foo expands to \csname foo-wibble\endcsname and the tokens are expanded again in the order marked, so after three more expansion steps you get

%1
 \expandafter
 \ifx
%2
 \csname foo-wibble\endcsname\relax

So after two expansion steps the input buffer has

\ifx
\foo-wibble\reax

which is true if \foo-wibble was not previously defined.

As you see here you needed to expand the token three times, but at each step to apply another \expandafter you neeed to chain past all the existing ones, so if you had needed four expansions you would need \expandafter in front of each of the existing 7, then one \expandafter to apply to the token, so 2*7+1=15 which is again one less than a power of two.


If you use expl3 you would probably just use an :e function instead of :n and not need any \expandafter at all, but this is the classic way of chaining \expandafter


You could replace

\expandafter\def\csname macro-2\endcsname{\csname test-2\endcsname}

by

\expandafter\def\csname macro-2\expandafter\endcsname\expandafter{\csname test-2\endcsname}

then you would need one less expansion later so just three not 7 chained expandafter or you could use

\expandafter\let\csname macro-2\expandafter\endcsname\csname test-2\endcsname

which would remove another layer of expansion, so you would only need one \expandafter later not 7.

David Carlisle
  • 757,742
  • Thanks for the answer. As to the lower part: This might work in the OP's reduced code, but unfortunately, in my real application, \test-2 is more complicated and may contain variable code itself, like \cname test-\csname subtest-\the\my@counter\endcsname\endcsname. I need to use the \IfComp conditional to test whether \macro-2 is defined depending on a particular value of \my@counter. – Lupino Mar 06 '24 at 19:32
  • 2
    @Lupino yes sure I guessed your real code was more complicated but as a general rule if expandafters are building up you can usually move some of them to the intermediate definitions, although the details vary, as you say. Also one of the main motivating ideas of the whole expl3 syntax is that you should never have to see a chain of expandafter. – David Carlisle Mar 06 '24 at 19:43
  • yea, I know. Comming to terms with expl3 is on my long-term todo list, but for now, I need to get CoCoTeX to run... – Lupino Mar 06 '24 at 19:45
  • thinking of which; does expl3 provide a way to expand a certrain control structure exactly n times (no more, no less)? – Lupino Mar 06 '24 at 19:48
  • 1
    @Lupino only once or many, relying on a fixed number makes very fragile code – David Carlisle Mar 06 '24 at 20:24