通过 \def 宏不执行任何操作

通过 \def 宏不执行任何操作

我遇到了另一个扩展问题,并将其缩小到这个简单的宏测试用例,该宏确实没有什么,至少它尝试什么都不做,但并没有那么成功。

如果我将此宏声明为:

\newcommand*{\DoNothingA}[1]{#1}%

效果非常好,没有什么意外。

但是,如果这个宏定义如下:

\newcommand*{\DoNothingB}[1]{% 
    \def\Temp{#1}%
    \Temp%
}%

然后,当尝试在 中使用它时,就会出现问题\edef

输出:

请注意,下面的 MWE 在最后一步产生了编译错误,因此您需要跳过<RETURN>它才能看到输出(或者注释掉最后一个\edef,但这样就不会显示下面的问题 2)。

下面的 MWE 得出以下结论:

在此处输入图片描述

最后一行尤其令人好奇,因为它产生了foo 4,并且结果是:

\edef\NewTemp{\DoNothingA{foo 5}}
\NewTemp
\color{red}% Highlight problem output
\edef\NewTemp{\DoNothingB{foo 6}}
\NewTemp 
\color{black}
~-- used \verb|\edef| to store output of macro

问题:

  1. 这里的扩展问题如何解释?
  2. 如果我们跳过编译错误,最终的输出会变成什么样foo 4foo 5会更容易接受)?
  3. 大卫·卡莱尔曾说过“多吃一点\expandafter就能治愈所有已知的疾病”:-),这里定义的\expandafter最后\edef使用宏所需的 s序列是什么。\DoNothinB
  4. 我如何调整\DoNothingB{}宏以使用临时变量并仍然在 MWE 中的三种情况下工作(最好不必使用任何\expandafter技巧)。

代码:

\documentclass{article}
\usepackage{xcolor}% To highlight problem output

\newcommand*{\DoNothingA}[1]{#1}% Does everything I want in all cases

\newcommand*{\DoNothingB}[1]{% Works mostly, except if used in a \edef
    \def\Temp{#1}%
    \Temp%
}%

\begin{document}
% These two work great.
\DoNothingA{foo 1}
\DoNothingB{foo 2}
-- used output of macro directly

% These two also work great.
\def\NewTemp{\DoNothingA{foo 3}}
\NewTemp
\def\NewTemp{\DoNothingB{foo 4}}
\NewTemp
~-- used \verb|\def| to store output of macro


\edef\NewTemp{\DoNothingA{foo 5}}
\NewTemp
\color{red}% Highlight problem output
\edef\NewTemp{\DoNothingB{foo 6}}
%\show\NewTemp% Excellent suggestion by David Carlisle yields: \def foo 4{foo 6}foo 4
\NewTemp 
\color{black}
~-- used \verb|\edef| to store output of macro
% Why is the last output "foo 4" (after ignoring error)??
\end{document}

答案1

简短回答:您不能在可扩展上下文中使用诸如\def或之类的赋值,因为它们不可扩展!\edef\edef

长答案:
扩展\edef其内容,直到只剩下不可扩展的标记(请注意,文本不可扩展)。\def是一项分配,所有分配都必须执行因此不是可扩展。你的\DoNothingB实际上做了一些事情:它定义了\Temp

让我解释一下以下例子中发生的情况:

\def\Temp{before}
% ...
\edef\NewTemp{\def\Temp{something}}

然后,\edef首先尝试扩展\def,这是不可扩展的,因此保持原样,然后\temp扩展到before,然后是其余部分:标记{so、...、g},它们都是不可扩展的。因此,您将得到,一旦扩展(即使用),它将指示 TeX 使用参数文本和替换文本进行定义\NewTemp\def before{something}因为是不可变的(只要它没有被声明为活动字符),您将在此处收到错误。\NewTempbeforesomethingb

在您的示例中,在第四个示例中,您在之外\Temp使用了 。在( )内部的下一个用法中,您会得到 ,因为两个用法都立即展开,这会导致上述错误。作为错误处理的一部分, 被丢弃,然后进行排版。 如果在输出之前定义不同,则输出将一致。foo 4\DoNothingB\edef\edef\edef\NewTemp{\DoNothingB{foo 6}}\def foo4{foo 6}foo4\NewTemp\Temp\def foo4{foo 6}foo4\Temp

这里几个\expandafters 没有帮助,因为顺序不是这里的问题,并且\edef无论如何都会不断扩展所有内容,直到只剩下不可扩展的标记。

但是,您可能希望在宏\noexpand之前添加一些直接代码,以防止它们在第一个 中过早扩展。现在这在 中不起作用。通常,您可以改用LaTeX 。根据上下文,是(外部) 还是(内部),这类似于。对于 的使用,您需要自己测试此模式,并在 内部手动添加一个。\Temp\DoNothingB\edef\def\protect\protected@edef\protect\relax\protected@edef\@unexpandable@protect\protected@edef\noexpand\def\noexpandedef

以下代码可实现此目的。请注意,您需要将所有 替换\edef\protected@edef才能使其正常工作。正常\edef和其他扩展上下文(如\message或 )\write将不起作用。

\documentclass{article}
\usepackage{xcolor}% To highlight problem output

\newcommand*{\DoNothingA}[1]{#1}% Does everything I want in all cases

\makeatletter
\newcommand*{\DoNothingB}[1]{% Works mostly, except if used in a \edef
    \ifx\protect\@unexpandable@protect
        \def\noexpand\Temp{#1}%
    \else
        \def\Temp{#1}%
    \fi
    \protect\Temp%
}%
\makeatother

\begin{document}
% These two work great.
\DoNothingA{foo 1}
\DoNothingB{foo 2}
-- used output of macro directly

% These two also work great.
\def\NewTemp{\DoNothingA{foo 3}}
\NewTemp
\def\NewTemp{\DoNothingB{foo 4}}
\NewTemp
~-- used \verb|\def| to store output of macro


\edef\NewTemp{\DoNothingA{foo 5}}
\NewTemp
\color{red}% Highlight problem output
\makeatletter
\protected@edef\NewTemp{\DoNothingB{foo 6}}
\makeatother
\NewTemp 
\color{black}
~-- used \verb|\edef| to store output of macro
% Why is the last output "foo 4" (after ignoring error)??
\end{document}

答案2

\newcommand*{\DoNothingA}[1]{#1}

\newcommand*{\DoNothingB}[1]{\def\Temp{#1}\Temp}

\begin{document}

\DoNothingA{foo 1}
\DoNothingB{foo 2}

根据定义,这将打印“foo 1”和“foo 2”,并定义\Temp扩展为foo 2

\def\NewTemp{\DoNothingA{foo 3}}
\NewTemp

这定义\NewTemp为扩展为\DoNothingA{foo 3},因此\NewTemp打印“foo 3”

\def\NewTemp{\DoNothingB{foo 4}}
\NewTemp

这确实

\DoNothingB{foo 4}

定义\Temp扩展为foo 4并打印“foo 4”

\edef\NewTemp{\DoNothingA{foo 5}}
\NewTemp

让我们慢慢来。\edef导致 的扩展\DoNothingA,找到它的参数并被替换文本替换; 最终等同于说

\def\NewTemp{foo 5}

现在乐趣开始了。

\edef\NewTemp{\DoNothingB{foo 6}}

这里\DoNothingB找到它的参数并替换为

\def\Temp{foo 6}\Temp

\edef不满足,并\Temp用其替换文本替换了两个实例,替换文本仍然是,foo 4因此 TeX 执行了相当于

\def\NewTemp{\def foo4{foo 6}foo 4}

现在打电话

\NewTemp 

会尝试做

\def foo4{foo 6}foo 4

结果是一个错误消息:由于没有控制序列跟随\def,TeX 插入\inaccessible并执行

\def\inaccessible foo4{foo 6}foo 4

定义控制序列后,结果将打印“foo 4” \inaccessible

所以这就是 1 和 2 的答案。3 和 4 的答案很简单:您不能在 中对\edef任意数量的\expandafters 执行赋值,因为 TeX 在扩展期间不执行赋值,而只在处理的后期执行。总之:您不能在执行 时更改控制序列的含义\edef

解决方法

如果你想给\DoNothingB一个容器喂食,\edef你必须决定它在这种情况下应该做什么。如果你只是希望它在容器内部不做任何特别的事情,\def那么

\protected\def\Temp{} % initialization
\newcommand*\DoNothingB[1]{\protected\def\Temp{#1}\Temp}

可能会做;如果你说

\edef\NewTemp{\DoNothingB{foo}}

这将扩展\DoNothingB为令牌列表

\protected\def\Temp{foo}\Temp

但现在所有 token 都是不可扩展的,所以\edef相当于

\def\NewTemp{\protected\def\Temp{foo}\Temp}

如果\def\NewTemp{\DoNothingB{foo}}调用,\NewTemp则在第一个扩展步骤之后执行与之前相同的操作。请注意,必须将其初始化\Temp\protected宏,否则会出现类似

\edef\NewTemp{\DoNothingB{foo}}

会导致出现“未定义的控制序列”的错误消息。

当然,这是假设的前提是参数是\DoNothingB由不可扩展的标记组成的,因为它们不会受到扩展保护。

相关内容