何时使用 \edef、\noexpand 和 \expandafter?

何时使用 \edef、\noexpand 和 \expandafter?

我很乐意破解 TeX 宏,并将不同样式文件中的零碎部分拼凑在一起以满足自己的目的,但我怀疑我最终的破解并不尽如人意。特别是关于何时扩展宏以及何时不扩展宏。

我经常会定义一个元命令,然后定义一大堆子命令。因此名称子命令将包含参数,具体取决于传递给元命令的参数,以及内容子命令的执行也会根据元命令所得到的结果而略有不同。通常它不会是直接替换,而是“如果是则#1执行a\this否则执行\that”,但\this需要\that在定义时进行扩展,而不是在调用时。

这是一个非常简单的例子,仅涉及直接替换:

\def\cohtheory#1{
\expandafter\newcommand\expandafter{\csname #1func\endcsname}[1][*]{%
\MakeUppercase{#1}^{##1}}
}

有时我会担心我的命令过于复杂。例如,如果我想调用一个带有两个参数的命令,并且参数在命令之前展开,那么我的编码方式如下:

\expandafter
  \expandafter
  \expandafter
  \command\expandafter
            \expandafter
            \expandafter
              {\expandafter
                \argone
                \expandafter
              }\expandafter
              {\argtwo}

因此,我正在寻找有关何时以及如何控制定义宏时的扩展的指导。我强烈怀疑无法用简单的答案给出这样的答案,因此,为了让这个问题更有针对性,让我这样表述:

哪里有关于编写 TeX 宏的良好参考,其中包含有关如何最好地处理扩展的建议?

当然,如果有人能简短地回答一些建议,我会非常乐意阅读。

(注:这部分是受到 juannavarroperez 改编我的回答的启发这个问题其中 20 多个\expandafter秒被压缩为仅 1!)

答案1

扩展是 TeX 编程的一个复杂领域。我将首先尝试解释所涉及的关键原语,然后尝试提出一些示例。

\expandafter语会将下一个标记扩展。因此

\expandafter\def\csname an-awkward-name\endcsname

\csname先扩展\def。因此,经过一次扩展后,上面的内容变为

\def\an-awkward-name

然后它就会发挥作用。当你想更进一步时,生活会变得更加复杂,而且很快就会很难追踪正在发生的事情。

>原\edef语对作为其参数给出的内容进行完全扩展(与 不同\def, 仅存储输入)。因此

\def\examplea{more stuff}
\edef\exampleb{Some stuff \csname examplea\endcsname}

将扩展\csname name\endcsname\examplea,然后扩展其为留下的最终定义为\exampleb“一些东西更多的东西”。

现在,通过阻止对下一个标记进行扩展来\noexpand引入。因此,如果我将上面的例子修改为\edef

\def\examplea{more stuff}
\edef\exampleb{Some stuff \expandafter\noexpand\csname examplea\endcsname}

那么将会发生什么呢?将\edef执行\expandafter,这将有效地将上述内容变成

\def\examplea{more stuff}
\edef\exampleb{Some stuff \noexpand\examplea}

现在\noexpand将运行(在此过程中消失),留下的定义\exampleb作为“一些东西 \examplea”。

我们可以利用这种能力来减少\expandafter使用,但还有其他一些事情需要了解。首先,e-TeX 包含一个额外的原语\unexpanded,这将防止扩展多个标记。其次,在各种特殊情况下,您不需要那么多\expandafter语句。一个典型的例子是来自内部\csname,因为这无论如何都会进行扩展。所以你会看到类似

\csname name\expandafter\endcsname\token

它将会\token在 之前扩展\name

回到你的例子。在第一个例子中,没有太多事情要做:因为整个要点是要有一个动态名称( ),所以在定义点#1执行操作实际上没有意义。最接近的例子是\edef

\edef\cohtheory{%
  \noexpand\newcommand\expandafter\noexpand\csname foofunc\endcsname[1][*]{%
  \noexpand\MakeUppercase{foo}^{##1}}%
}

这里将会发生的情况是\newcommand\MakeUppercase将受到保护而不会扩展,并且\csname只会扩展一次。(没有扩展的令牌不需要保护,这就是为什么像“[1]”这样的东西只是按原样包含在内。)当然,这有点像“玩具”,因为它所做的就是创建一个固定的\foofunc

对于第二个示例,您可以这样做:

\begingroup
  \edef\temp{%
    \endgroup
    \noexpand\command
    {\unexpanded\expandafter{\argone}}%
    {\unexpanded\expandafter{\argtwo}}%
  }
\temp

我在这里使用了一些额外的想法。首先,使用组是为了在\temp我使用它的地方之外的任何地方都不会改变。\endgroup原始元素在内部不会执行任何操作,因此在使用\edef时仍会在那里关闭组。其次,像 toks 一样工作,因此会尊重它之后但在 之前。这减少了不必要的。\temp\unexpanded\expandafter{\expandafter

这个问题还有很多问题,而且通常有几种同样有效和明确的方法。你最好发布具体的例子,并寻求有关如何实现这些方法的建议。

答案2

\expandafter和的一个区别\edef是它们对受保护的宏的行为。

eTeX 提供了前缀\protected,它可在 和 之前使用,\def以定义受保护的,即“健壮”的宏,该宏不会在上下文中扩展\edef(例如\write)。但是,\expandafter会扩展这样的宏。

参见以下示例(适用于 eTeX 和 LaTeX):

\protected\def\pempty{}

\edef\withedef{x\pempty x}
\expandafter\def\expandafter\withexpandafter\expandafter{\expandafter x\pempty x}

\tt
\meaning\pempty.

\meaning\withedef.

\meaning\withexpandafter.

给出:

\protected macro:->.
macro:->x\pempty x.
macro:->xx.

还有一些 TeX 扩展标记的情况,例如在之后&\cr内部\halign。此处 TeX 在找到受保护的宏而不展开它时停止。但是,如果 TeX 在数字读数\ifnum保护宏的模式也得到了扩展。

答案3

对于动态创建控制序列的特殊问题,我建议你使用类似

\def\csarg #1{%
    \begingroup
    \expandafter
    \endgroup
    \expandafter #1\csname
}

你使用它就像

\csarg\mycommandbuilder <whatever> \endcsname

你甚至可以像这样使用它

\csarg\mycommandbuilder <whatever> \expandafter\endcsname
    \csname <whatever2> \expandafter\endcsname
    \csname <whatever3> \endcsname

但是,要小心空格。上面的代码构造了名称中有空格的控制序列!

答案4

为了使答案完整,我们必须知道,TeX 使用作用域,即定义宏的区域。如果你离开该区域,宏(当然还有它的内容)将被销毁。让我们举个例子:

\def\a{foo}
{
  \def\a{bar}
  The actual content of \verb|\a| is \a
}
The actual value of \verb|\a| is \a

将产生

第一个示例输出

原因是大括号{}构成了一个新的范围。在这个范围内,宏\a是局部的,也就是说:它是一个全新的宏,与仍存在的同名但位于不同范围内的宏相比,它存储的内容不同。因此,第一次调用的\a结果内容是“bar”。范围区域结束后,局部变量被销毁,属于周围范围的宏被“恢复”。\a从外部范围第二次调用将显示最初存储在的内容\a:“foo”。

如果你需要在不同的作用域中处理相同的值,则必须使用全局变量。下面是另一个示例,以更清楚地说明这一点:

%% Second example with \b
{
  \global\def\b{baz}
  %% Do some things on \b
}
The actual value of \verb|\b|, defined in a not more existing scope is
still `\b'.

结果如下:

第二个示例输出

除了上面的更为文字化的代码,我还可以使用更短的形式\gdef

%% Second example with \b
{
  \gdef\b{baz}
  %% Do some things on \b
}
The actual value of \verb|\b|, defined in a not more existing scope is
still `\b'.

结果还是一样:

第二个示例输出

请参阅其他(非常好!)关于普通\def命令与其同伴扩展版本之间的区别的答案\edef。当然,您可以在本地范围或全局范围内使用扩展宏玩游戏:

%% Third example with \edef and \global\edef
%% Define the variable \foo to show, where and why this is happening.
%% First: the value before the new scope
\def\foo{before}
\def\a{\foo}
\edef\b{\foo}
\def\c{}
\def\d{}
{
  %% Second: within the scope
  \def\foo{within}
  \def\c{\foo}
  \edef\d{\foo}
  \global\def\e{\foo}
  \global\edef\f{\foo}
}
%% Third: after the scope was left, now again in the global scope
\def\foo{after}

Results 
\begin{tabular}{@{} cc @{}}
  \toprule
  \multicolumn{1}{@{} H}{Variable} & \multicolumn{1}{H @{}}{Content} \\
  \midrule
  a & \a \\
  b & \b \\
  c & \c \\
  d & \d \\
  e & \e \\
  f & \f \\
  \bottomrule
\end{tabular}

毫不奇怪,当在范围内操作变量\c和时,它们\d仍然为空,因为它们尚未被定义:\global

第三个示例是 \global 和 \edef

当然,我也可以节省打字的力气,用 替换\global\def\gdef见上文)和\global\edef\xdef所以这是一个包含所有四种 def 的示例:\def\edef和。 \gdef\xdef

相关内容