[Tex/LaTex] Advantages and disadvantages of fully expandable macros

best practicesexpansionmacrostex-core

As I currently understand them, fully-expandable macros are analogous to pure/effect-free functions in functional programming. Correspondingly, things that are not expandable, such as \def, are effectful computations — they can't be "evaluated" inside an \edef, for example. Effect-free functions are generally beneficial (think immutable objects in OOP), and I would expect this to carry over to fully-expandable TeX macros.

However, writing fully-expandable macros seems to be tricky in many cases (see, for example, Tricks to make macros expandable). Moreover, the documentation of the xparse package seems to suggest that fully-expandable macros are usually not desirable:

There are very rare occasion when it may be useful to create functions using a
fully-expandable argument grabber. […] This facility should only be used
when absolutely necessary; if you do not understand when this might be, do not
use these functions!

So, when should I use fully-expandable macros? What are the advantages and disadvantages of fully-expandable macros?

Best Answer

I think it is best not to compare the expandable/not expandable distinction to concepts from other languages. The main issues relating to expansion are really particular (some would say peculiar) to the execution model of TeX.

TeX has two main modes of operation. All assignments and boxing operations happen (in "the stomach" in The TeXBook terminology) as non-expandable operations. Macro expansion happens before that but unlike (say) the macro expansion of the C pre-processor, macro expansion and non-expandable operations are necessarily intertwined.

It is probably worth noting that the question as posed is not well defined.

TeX tokens are either expandable or non-expandable but "fully expandable" is a grey area full of traps into which the unwary may fall.

Any token defined by \def (or \newcommand etc) is by definition expandable.

A character token such as a is by definition non-expandable.

\def is a non-expandable token.

So if you define

\def\zza{}
\def\zzb{a}
\def\zzc{\def\zze{}}
\def\zzd{\ifmmode a \else b\fi}

then each of these is expandable, with expansion <nothing> a \def\zze{} \ifmmode a \else b\fi respectively.

However which of these is fully expandable ?

Clearly \zza is. But if the definition of "fully expandable" means may be expanded repeatedly leaving no unexpandable tokens then the only fully expandable tokens will all expand to nothing.

So most preople would class \zzb as fully expandable, even though it expands to a which is not expandable.

So a better (or at least more accurate) term than "fully expandable" is "safe in an expansion-only context". Inside \edef and \write and when TeX is looking for a number or dimension, and a few other places, TeX only does expansion and does not do any assignment or other non-expandable operations.

 \edef\yyb{\zzb}

is of course safe, it is the same as \def\yyb{a}. So \yyb is safe in an expansion-only context.

\edef\yyc{\zzc}

is not safe, it is the same as

\edef\yyc{\def\zze{}}

Now \def doesn't expand but in an expansion-only context the token just stays inert it does not make a definition so TeX then tries to expand \zze which typically is not yet defined so this leads to an error, or if \zze has a definition then this will be expanded which is almost always unwanted behaviour. This is the basic cause of the infamous "fragile command in a moving argument" errors in LaTeX.

So \zzc is not safe in an expansion-only context. If it had been defined by the e-TeX construct

\protected\def\zzc{\def\zze{}}

Then in an expansion-only construct protected tokens are made non-expandable so

\edef\yyc{\zzc}

would then be safe, and the same as \def\yyc{\zzc} So a protected command is safe in an expansion-only context but since this safety comes by making the token temporarily non-expandable it probably isn't accurate to say it is "fully expandable".

\edef\yyd{\zzd}

is

\edef\yyd{\ifmmode a \else b\fi}

which is

\def\yyd{b}

or \def\yyd{a} if the definition is happening inside $...$ (or an equation display). Similarly it will expand to b at the start of an array cell as the expansion will happen while TeX is expanding looking for \omit (\multicolumn) and so before it has inserted the $ to put the array cell in to math mode. Again a protected definition to limit expansion is what is required here.

So sometimes it is good to make things expandable as it keeps more options open.

\def\testa#1#2#3{%
  \ifnum#1=0
  \def\next{#2}%
  \else
  \def\next{#3}%
  \fi
  \next}

\def\firstoftwo#1#2{#1}
\def\secondoftwo#1#2{#2}

\def\testb#1{%
  \ifnum#1=0
  \expandafter\firstoftwo
  \else
  \expandafter\secondoftwo
  \fi}

both \testa{n}{yes}{no} and \testb{n}{yes}{no} will exectute yes if n is 0 and no otherwise but \testb works by expansion and so is safe in an expansion-only context (if its arguments are safe). The \testa version relies on the internal non-expandable operation of \def\next. (Plain TeX and LaTeX2.09 use many tests using \def\next, LaTeX2e changed them to the expandable form where possible.)

For a numeric test it is easy to use the expandable form, but if you want to test if two "strings" are equal by far the easiest way is to go

\def\testc#1#2{%
  \def\tempa{#1}\def\tempb{#2}%
  \ifx\tempa\tempb
  \expandafter\firstoftwo
  \else
  \expandafter\secondoftwo
  \fi}

but now even though we have used the \expandafter\firstoftwo construct, the test relies on two non-expandable definitions. If you really need to test in an expandable way you can find some questions on this site but any answer is typically full of special conditions and cases where it doesn't work and relies on some kind of slow token by token loop through the two arguments testing if they are equal. In 99% of the cases this complication is just not needed and the non-expandable test is sufficient. If you are trying to define a consistent set of tests (as in the ifthen package \ifthenelse for example, then if you resign yourself to the fact that some tests are necessarily non-expandable then you may choose to make them all non-expandable so they behave in a consistent way.

So the answer is:

It all depends....