为什么使用 \expandafter\@firstoftwo 习语?

为什么使用 \expandafter\@firstoftwo 习语?

我看到很多宏都是条件测试,后跟一{true}{false}对定义,因此它们的结果如下:

\expandafter\@firstoftwo

或者

\expandafter\@secondoftwo

为什么这些\expandafters 在那里?我本以为它们只会捕获下一{true}{false}对中的第一个括号?

答案1

s\expandafter用于处理以下\else\fi。正如 Ryan 链接到的问题中所述,完整代码如下:

\def\ifeq#1#2{%
 \ifx#1#2\relax
   \expandafter\@firstoftwo
 \else
   \expandafter\@secondoftwo
 \fi
}

让我们来追踪一下发生了什么。我们将其放入\ifeq\stuff\nonsense{true}{false}我们的文档中。\ifeq吸收了\stuff,因此\nonsense在第一次扩展之后我们得到了:

\ifx\stuff\nonsense\relax
  \expandafter\@firstofone
\else
  \expandafter\@secondoftwo
\fi
{true}{false}

假设是\stuff\nonsense即条件为真)。然后\ifx开始扩展其“真”路径中的所有内容,该路径被定义为直到下一个\else\fi(模数嵌套)的所有内容。关键点是它开始扩展第一的并且不会提前查找\else\fi。TeX 认为当它到达时就会知道。所以我们有:

  \expandafter\@firstoftwo
\else
  \expandafter\@secondoftwo
\fi
{true}{false}

TeX 现在扩展了。这具有从 延伸到并扩展\expandafter的效果。“扩展”意味着从流中移除它以及匹配之前的所有内容。所以我们剩下:\@firstoftwo\else\else\fi

\@firstoftwo{true}{false}

然后,它就会扩展为简单的true

没有\expandafter了 s,我们得到:

  \@firstoftwo
\else
  \@secondoftwo
\fi
{true}{false}

TeX 仍在扩展真正的分支,因此会扩展\@firstoftwo。这会从流中吸收两个标记/括号组。它们恰好是\else\@secondoftwo。然后它将第一个留在流中,因此我们得到

\else
\fi
{true}{false}

\else与条件匹配,因此 TeX 会吸收此内容以及流中留下的所有内容。\fi但这{true}{false}并不是我们想要的。

总之,\expandafters结果条件被扩展,从而确保条件的结果看到流中的下一个位,而不是未完成条件留下的位。

答案2

我会从不同的角度来解决这个问题。TeX 中的原始条件测试一个条件,该条件可以从输入流中吸收标记,或者直到可以确定条件的真假为止。因此,让我们用 <IF>原始条件和必须吸收的标记(可能为空)列表表示。例如,\ifhmode不需要标记,\ifx需要两个。在某些情况下(\if\ifcat\ifnum\ifdim),TeX 会执行扩展以找到测试所需的标记类型;在其他情况下(\ifx\ifmmode\ifhmode\ifvmode \ifinner\iftrue\iffalse)不执行扩展。因此<IF>将表示条件和所需的标记扩展已经发生并且可以测试条件。

你所指的典型结构是

<IF>
  \expandafter\@firstoftwo
\else
  \expandafter\@secondoftwo
\fi

我们已经有了

\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}

更一般地,我们有

<IF><true>\else<false>\fi

或者

\IF<true>\fi

其中和<true>都可<false>以为空。

条件为真

TeX 将简单地<IF>从输入流中删除,留下

<true>\else<false>\fi

或者

<true>\fi

条件为假

TeX 会查找下一个\else标记,同时考虑可能出现的嵌套条件,而<true>无需扩展任何内容。因此,\else属于嵌套\if...\else\fi内部的<true>标记将被跳过。在这种情况下,扩展也是空的,并且直到的每个标记\else都将消失。如果\else找不到匹配项,TeX 将停止查找应该在某处的匹配项\fi。因此,在这两种情况下,我们都会得到

<false>\fi

或者如果没有\else分支则什么也没有。

以下 TeX 输入证明了这一点:

\def\showx{\show\x}
\def\showif{\afterassignment\showx
  \expandafter\def\expandafter\x\expandafter}

\showif{\ifvmode<true>\else<false>\fi}
\showif{\ifvmode<true>\fi}
\showif{\ifhmode<true>\else<false>\fi}
\showif{\ifhmode<true>\fi}
\bye

在其上运行 TeX 将产生以下记录:

This is TeX, Version 3.1415926 (TeX Live 2012)
(./plkfi.tex
> \x=macro:
-><true>\else <false>\fi .
\showx ->\show \x

l.5 \showif{\ifvmode<true>\else<false>\fi}

?
> \x=macro:
-><true>\fi .
\showx ->\show \x

l.6 \showif{\ifvmode<true>\fi}

?
> \x=macro:
-><false>\fi .
\showx ->\show \x

l.7 \showif{\ifhmode<true>\else<false>\fi}

?
> \x=macro:
->.
\showx ->\show \x

l.8 \showif{\ifhmode<true>\fi}

?

接下来发生什么

  • 的扩展\else包括删除匹配之前的所有内容\fi,并且在输入流中不留下任何内容。嵌套条件将像以前一样被考虑。

  • 的展开\fi为空。

的作用\expandafter

现在,我们已经有了可以投放的基地\expandafter

让我们看一个典型的用法:

\def\@ifundefined#1{%
  \expandafter\ifx\csname#1\endcsname\relax
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi}

我们想要的是能够说

\@ifundefined{foo}{T}{F}

因此,将使用参数作为名称构建的宏进行比较\relax(这实际上是无趣的部分),然后遵循真或假分支。

移除后<IF>我们仍然

\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi{T}{F}

现在 TeX 会适时扩展第一个标记。这会触发扩展\else,而这正是好玩的地方。

的扩展\expandafter包括扩展(如果可能的话)下一个标记之后的标记并消失。因此\else根据上述规则进行扩展,我们剩下

\@firstoftwo{T}{F}

这导致留T在输入流中。

现在假设条件为假。然后将<IF>连同直到的所有内容一起删除\else,剩下

\expandafter\@secondoftwo\fi{T}{F}

现在\expandafter它的工作就是扩大\fi和消失。因此我们得到

\@secondoftwo{T}{F}

终于离开了F

重要的提示

\@ifundefined{foo}{T}{F}我们能够得到TF没有执行命令:只使用了宏扩展。这使得\@ifundefined类似定义的宏可以在内部使用\edef

\edef\test{\@ifundefined{foo}{T}{F}}

相当于

\def\test{T}

如果\foo定义了(而不是等同于\relaxLaTeX 中的通常情况),或者

\def\test{F}

如果\foo未定义(或等同于\relax)。


如果没有会发生什么\expandafter?如果条件成立,TeX 将面临

\@firstoftwo\else\@secondoftwo\fi{T}{F}

并且 的两个参数将\@firstoftwo\else\@secondoftwo,这不会做任何有用的事情,不是吗?

类似地,对于错误条件,我们会得到

\@secondoftwo\fi{T}{F}

事情又会出错。

答案3

此类代码的完整上下文位于条件宏中,例如

\def\IfZero#1{%
  \ifnum0=#1\relax
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}

\expandafter如果你以不太格式化的方式查看代码,则 s 是必需的:

\def\IfZero#1{\ifnum0=#1\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}

(抱歉,代码很长)其中没有给出 TeX 条件块的结构线索。如您所见,标记“after”\@firstoftwo\else,而 after\@secondoftwo\fi,它们分别被 扩展\expandafter。这种愚蠢的做法的目的是 TeX 在扩展 时不会读取整个条件\ifnum;它只是向前扫描到正确的 true 或 false 块并从那里继续。\elseor\fi留在输入流中!一旦它们被扩展,TeX 就会扫描到条件的末尾,输入流中的下一个内容是 后面的内容\IfZero

如果没有,和\expandafter所消耗的“两个”将分别是和以及下面的宏参数,而不是实际需要的,即作为 的“第二”和“第三个”参数的以下两个宏参数。\@firstoftwo\@secondoftwo\else\@secondoftwo\fi\IfZero

相关内容