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.
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.
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.
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
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!
The cprotect package can be used in most cases, see the link above.
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
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}} \ExplSyntaxOffIf it were expandable, you would see
truebeing logged. However, it gives an error, which means the command is not expandable i.e. this case is the error.
If your code consist of some function that you wrote yourself, that has two parts...
\newcommand \myfunction[1]{
% part 1: something to <b>**set some variable**</b>, which could be...
\pgfmathparse{1+1} % e.g. <a href="https://tex.stackexchange.com/q/651836/250119">1</a> <a href="https://tex.stackexchange.com/q/69962/250119">2</a> <a href="https://tex.stackexchange.com/q/595394/250119">3</a>, or...
\DTLgetvalue{\thevalue}{database}{shortcut}{something} % e.g. <a href="https://tex.stackexchange.com/q/632603/250119">1</a>, or...
\FPpow \myresult{2}{3} % e.g. <a href="https://tex.stackexchange.com/q/48198/250119">1</a>, or...
\regex_replace_all:nnN {a} {b} \mytokenlist
% part 2: use that <i>"variable"</i>
\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
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} } \ExplSyntaxOffthen
\myresultis expandable (it expands totruein this case), and you can do\StrLeft{\myresult}{1}safely.Another example: If you want to pass the result of
\StrLeftto another macro, the documentation of\StrLeftmentions 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
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.
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.
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:nto:e. Other methods including\exp_argsorExpandArgs.
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? .
\outer, but they're extremely rare.
– user202729
May 29 '22 at 15:11
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.
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.
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"
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.
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
May 30 '22 at 12:01