答案1
一般来说,有几种情况。
这个答案主要针对那些想了解更多有关 LaTeX 编程的人。大多数修复都需要一些编程知识。
1 内部宏通过改变 catcode 来解析参数。
例如,在 中\textbf{\verb+123+}
,\verb
不能放在大多数其他命令的参数中,因为\verb
更改 catcode 来解析参数,除非采取特殊措施,例如\cprotect
。
1.1 如何发现此案例
一般来说,这种“逐字逐句”的命令都有明确的标记。如果这还不够,请阅读下面的部分。
您还可以尝试将其放入某些无害的外部宏的参数中来测试它。
\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 如何修复
cprotect 包在大多数情况下都可以使用,请参阅上面的链接。
2 内部宏不是可扩展。
这种情况发生在你希望某个“函数”产生某个“值”并将该“结果”传递给“外部函数”时;但内部函数却没有可扩展。
例如(改编自\IfSubStr 作为另一个宏的参数使用时不起作用)
\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 如何发现此案例
这需要一些 (La)TeX 编程知识。
确定命令是否可以使用例如来扩展\tl_show:x
。
对于上面的具体例子,可以按如下方式完成
\ExplSyntaxOn \tl_show:x{\IfSubStr{ab}{a}{true}{false}} \ExplSyntaxOff
如果它是可扩展的,你会看到
true
正在记录。但是,它给出了一个错误,这意味着该命令不可扩展,即这种情况是错误的。
2.1.1 特殊情况
如果您的代码由您自己编写的一些函数组成,则该函数包含两部分......
\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
}
那么确实这种情况下。通常修复它需要将第一部分分离到外部:
\outerfunction{\myfunction} % fail, because it's the same as...
\outerfunction{\pgfmathparse{1+1} \pgfmathresult}
% ^^^^^^^^^^^ part 1
% ^^^^^^ part 2
如果你将第一部分移到外面,它可能会起作用:
\pgfmathparse{1+1} \outerfunction{\pgfmathresult}
% ^^^^^^^^^^ part 1
% ^^^^^^^ part 2
2.2 如何修复
您必须查看文档以找到允许将结果传递给其他命令的其他变体。
在这个特定的例子中,可以通过将“true”和“false”的代码替换为
\ExplSyntaxOn \IfSubStr{ab}{a}{ \tl_set:Nn \myresult {true} }{ \tl_set:Nn \myresult {false} } \ExplSyntaxOff
然后
\myresult
是可扩展的(在这种情况下它会扩展到true
),并且您可以\StrLeft{\myresult}{1}
安全地执行。另一个例子:如果你想将的结果传递
\StrLeft
给另一个宏,的文档\StrLeft
提到你可以提供一个尾随[⟨name⟩]
参数来将结果存储到该名称中。其余的与上面相同。
3 外部宏不扩展其参数。
在“正常编程语言”中,“函数”是从内到外进行“评估”的,因此 if sqrt(4) = 2
thenf(sqrt(4))
和f(2)
具有完全相同的行为。
然而,在 TeX 中,参数是逐字通过到外部命令。因此,如果外部命令决定不扩展参数,则会产生影响。
3.1 如何发现此案例
使用上面提到的方法(例如\tl_show:x
)检查内部宏是否真正可扩展。
如果是,请尝试替换扩展结果(使用正确的标记 catcodes!小心)放入宏的参数中,看看它是否有效。
3.2 如何修复
修复这种情况也需要一些 LaTeX 知识(尽管一般来说比上面的情况 2 简单),你可以安排自定义命令扩展到整个代码拟执行。
比如上面的例题3,当用户写入 时
\tl_trim_spaces:n { \tl_tail:N \l_tmpa_tl }
,被修剪的内容其实是\tl_tail:N \l_tmpa_tl
,而不是 的结果。在这种情况下,你可以明确安排扩展内容(即尾部)要传递给宏
\tl_trim_spaces:n
,一种方法是将外部更改:n
为:e
。其他方法包括\exp_args
或者ExpandArgs
。
4 外部宏“错误地扩展”了它的参数。(内部宏很脆弱。)
与上面的情况类似。如果外部宏尝试在仅扩展上下文中扩展内部宏,并且内部宏很脆弱,则它将无法正确运行。
答案2
如今,大多数现代编程语言都提供真正的函数,您可以通过函数组合来完成复杂的工作,因为函数的评估是从内到外的。
但是 TeX 是一种相当古老的宏语言,宏扩展是从外到内的,因此它让很多普通的 TeX 用户感到困惑和沮丧,因为(我猜)大多数 TeX 用户都曾经使用过至少一种现代编程语言。
几个月前我开始写作功能基于 的包expl3
,为 LaTeX 用户提供真正的函数式编程接口。该包中的所有函数都有返回值,并且函数的求值是从内到外的。
@user202729 在他的回答中展示了几种宏嵌套可能导致问题的常见情况。如果你正在修复一些旧代码,我建议你使用他提供的解决方案,但如果您正在编写一些新代码,我建议你尝试一下functional
这个包,除了情况 1。
情况 2:内部宏不可扩展。
代替
\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
你可以很容易地写
\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"
在写functional
代码的时候,你用\prgReturn
命令来返回函数的结果,并且可以在函数代码内部自由的进行赋值,不用担心赋值不可扩展。
情况 3:外部宏不扩展其参数
代替
\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
你可以很容易地写
\tlSet \lTmpaTl {a b c }
1\tlTrimSpaces {\tlVarTail \lTmpaTl}2 % you get correct result "1b c2"
情况 4:外部宏“错误地扩展”了其参数。(内部宏很脆弱。)
在functional
包中,函数(用 定义\prgNewFunction
)始终受到保护。外部函数无需手动“扩展”内部函数,因为functional
包会负责函数的组成。