7

I have some macro X and Y, which works well when used independently. However, when I try to put one inside another, it results in some error.

Why is that the case and how can I fix it?

Note: this is meant to be a generic question. Meta discussion.

2
  • 4
    Please add a case specific example. Question is unclear. Commented May 29, 2022 at 11:53
  • @MaestroGlanz I said it's meant to be general, see meta discussion.
    – user202729
    Commented May 29, 2022 at 12:01

2 Answers 2

14

Generally speaking, there are a few cases.

This answer is mostly meant for people who want to learn more about LaTeX programming. Most of the fixes require some programming knowledge.

1   The inner macro parses the argument by changing catcode.

For example, in \textbf{\verb+123+}, \verb cannot be put inside the argument of most other commands because \verb changes the catcode to parse the argument, unless special measures are applied e.g. \cprotect.

For example, those questions have this issue: 1 2

1.1   How to detect this case

Generally speaking such "verbatim-like" commands are clearly denoted. Read the part below if that's not sufficient.

You can also test it by try putting it inside the argument of some harmless outer macro.

\newcommand\harmless[1]{#1}

\framebox{123}  % typesets 123, okay

\harmless{\framebox{123}}  % still works

\verb+123+  % works okay when alone

\harmless{\verb+123+}  % error!

1.2   How to fix it

The cprotect package can be used in most cases, see the link above.

2   The inner macro is not expandable.

This case happens when you intend for some "function" to result in some "value" and pass that "result" to the "outer function"; but the inner is not expandable.

For example (adapted from \IfSubStr does not work when used as parameter of another macro)

\IfSubStr{ab}{a}{true}{false}  % works, result in "true" because "ab" contains "a"
\StrLeft{true}{1}   % works, result in "t"

\StrLeft{\IfSubStr{ab}{a}{true}{false}}{1}  % mysterious error

2.1   How to detect this case

This requires some (La)TeX programming knowledge.

Determine whether the commands are expandable by using e.g. \tl_show:x.

For the specific example above it would be done as the following

\ExplSyntaxOn
\tl_show:x{\IfSubStr{ab}{a}{true}{false}}
\ExplSyntaxOff

If it were expandable, you would see true being logged. However, it gives an error, which means the command is not expandable i.e. this case is the error.

2.1.1   A special case

If your code consist of some function that you wrote yourself, that has two parts...

\newcommand \myfunction[1]{

    % part 1: something to **set some variable**, which could be...
    \pgfmathparse{1+1}                                      % e.g. 1 2 3, or...
    \DTLgetvalue{\thevalue}{database}{shortcut}{something}  % e.g. 1, or...
    \FPpow \myresult{2}{3}                                  % e.g. 1, or...
    \regex_replace_all:nnN {a} {b} \mytokenlist

    % part 2: use that "variable"
    \myresult       % or...
    \pgfmathresult  % or...
    \tl_use:N \mytokenlist

}

then it's definitely this case. Usually fixing it involves separating the first part to outside:

\outerfunction{\myfunction}  % fail, because it's the same as...
\outerfunction{\pgfmathparse{1+1} \pgfmathresult}
%              ^^^^^^^^^^^ part 1
%                                 ^^^^^^ part 2

If you move the first part outside like this it might actually work:

\pgfmathparse{1+1}  \outerfunction{\pgfmathresult}
% ^^^^^^^^^^ part 1
%                                  ^^^^^^^ part 2

2.2   How to fix it

You have to look at the documentation to find other variants that allow you to pass the result to other commands.

In this particular example, it can be done by replacing the code in the "true" and "false" case with

\ExplSyntaxOn
\IfSubStr{ab}{a}{
  \tl_set:Nn \myresult {true}
}{
  \tl_set:Nn \myresult {false}
}
\ExplSyntaxOff

then \myresult is expandable (it expands to true in this case), and you can do \StrLeft{\myresult}{1} safely.

Another example: If you want to pass the result of \StrLeft to another macro, the documentation of \StrLeft mentions that you can provide a trailing [⟨name⟩] argument to store the result into that name. The rest is same as above.

Other examples of questions with this issue: 1 2 3

3   The outer macro does not expand its arguments.

In a "normal programming language", "functions" are "evaluated" from inside to outside, so if sqrt(4) = 2 then f(sqrt(4)) and f(2) has the exact same behavior.

In TeX, however, the argument is passed verbatim to the outer command. As such, if the outer command decide to not expand the argument, it will make a difference.

Example question: 1 2 3 4

3.1   How to detect this case

Use the method mentioned above (e.g. \tl_show:x) to check if the inner macro is really expandable.

If it is, try substituting the expansion result (with the correct token catcodes! Be careful) into the argument of the macro to see if it works.

3.2   How to fix it

Fixing this case also requires some LaTeX knowledge (although generally speaking simpler than the case 2. above), you can arrange for your custom command to expand to the whole code that is intended to be executed.

For example, in the example question 3 above, when the user write \tl_trim_spaces:n { \tl_tail:N \l_tmpa_tl }, the content that is being trimmed is actually \tl_tail:N \l_tmpa_tl, instead of the result of such.

In that case, you can explicitly arrange the expanded content (i.e. the tail) to be passed to the macro \tl_trim_spaces:n, one method is to change the outer :n to :e. Other methods including \exp_args or ExpandArgs.

4   The outer macro "incorrectly expands" its arguments. (the inner macro is fragile.)

Similar to the case above. If the outer macro attempts to expand the inner one in an expansion-only context, and the inner macro is fragile, it will not behave correctly.

This case is already extensively covered in What is the difference between Fragile and Robust commands? When and why do we need \protect? .

3
  • For some reading material, there are the TeXbook and expl3 programming guide (interface3.pdf), among others.
    – user202729
    Commented May 29, 2022 at 14:41
  • Of course there are other cases e.g. the inner macro is \outer, but they're extremely rare.
    – user202729
    Commented May 29, 2022 at 15:11
  • +1 Good question and answer. But it seems you can not stop the closure of the question.
    – L.J.R.
    Commented May 29, 2022 at 16:29
2

Nowaday most modern proramming languages provide real functions and you can do complicated jobs by function compositon, since the evaluation of functions is from inside to outside.

But TeX is a rather old macro language and macro expansion is from outside to inside, hence it confuses and frustrates plenty of ordinary TeX users, since (I guess) most TeX users have ever used at least one of the modern programming languages.

Several months ago I started to write functional package based on expl3, to provide real functional programming interface for LaTeX users. All functions in this package have return values, and the evaluation of functions is from inside to outside.

In his answer, @user202729 has showed several common cases where macro nesting may cause problems. If you are fixing some old code, I would suggest you to use the solutions provided by him, but if you are writing some new code, I would suggest you to give functional package a try, except for case 1.

Case 2: The inner macro is not expandable.

Instead of

\IfSubStr{ab}{a}{true}{false} % works, result in "true" because "ab" contains "a"
\StrLeft{true}{1} % works, result in "t"
\StrLeft{\IfSubStr{ab}{a}{true}{false}}{1} % mysterious error

you can easily write

\tlIfInTF{ab}{a}{\prgReturn{true}}{\prgReturn{false}}  % works, result in "true" because "ab" contains "a"
\tlHead{true} % works, result in "t"
\tlHead{\tlIfInTF{ab}{a}{\prgReturn{true}}{\prgReturn{false}}} % also works, result in "t"

In writing functional code, you use \prgReturn command to return the results of the functions. And you can freely do assignments inside the function code, without worrying that assignments are not expandable.

Case 3: The outer macro does not expand its arguments

Instead of

\ExplSyntaxOn
\tl_set:Nn \l_tmpa_tl {a~b~c~}
1\tl_trim_spaces:n {\tl_tail:N \l_tmpa_tl}2 % you get incorrect result "1 b c 2"
\ExplSyntaxOff

you can easily write

\tlSet \lTmpaTl {a b c }
1\tlTrimSpaces {\tlVarTail \lTmpaTl}2 % you get correct result "1b c2"

Case 4: The outer macro "incorrectly expands" its arguments. (the inner macro is fragile.)

In functional packages, functions (defined with \prgNewFunction) are always protected. The outer function need not to manually "expand" inner function since functional package will take care of the composition of functions.

1
  • Remark, case 2&3 works as long as both the outer macro and the inner macro are functions defined by the functional package. In the case they aren't, you probably want to learn how to use the package and define your own function to wraps the existing macro etc.
    – user202729
    Commented May 30, 2022 at 12:01

You must log in to answer this question.

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