1 内部宏通过改变 catcode 来解析参数。

1 内部宏通过改变 catcode 来解析参数。

我有一些宏 X 和 Y,它们单独使用时效果很好。但是,当我尝试将一个宏放入另一个宏中时,会导致一些错误。

为什么会出现这种情况?我该如何解决?

注意:这是一个一般性问题。元讨论

答案1

一般来说,有几种情况。

这个答案主要针对那些想了解更多有关 LaTeX 编程的人。大多数修复都需要一些编程知识。

1 内部宏通过改变 catcode 来解析参数。

例如,在 中\textbf{\verb+123+}\verb不能放在大多数其他命令的参数中,因为\verb更改 catcode 来解析参数,除非采取特殊措施,例如\cprotect

例如,这些问题有这个问题:1 2

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⟩]参数来将结果存储到该名称中。其余的与上面相同。

与此问题相关的其他问题示例: 1 2 3

3 外部宏不扩展其参数。

在“正常编程语言”中,“函数”是从内到外进行“评估”的,因此 if sqrt(4) = 2thenf(sqrt(4))f(2)具有完全相同的行为。

然而,在 TeX 中,参数是逐字通过到外部命令。因此,如果外部命令决定不扩展参数,则会产生影响。

示例问题:1 2 3 4

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 外部宏“错误地扩展”了它的参数。(内部宏很脆弱。)

与上面的情况类似。如果外部宏尝试在仅扩展上下文中扩展内部宏,并且内部宏很脆弱,则它将无法正确运行。

此案已在脆弱命令和坚固命令之间有什么区别?什么时候以及为什么我们需要 \protect?

答案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包会负责函数的组成。

相关内容