为什么 c 和 f 型扩展不尊重受保护的命令?

为什么 c 和 f 型扩展不尊重受保护的命令?

根据这个答案

所有没有星号的函数都是(或至少应该是)\protected,这样它们就不会扩展xe键入扩展或写入文件时。

fc类型扩展不同,并且忽略\protected

既然拥有尊重ed 命令的扩展类型(例如x和)被认为是有价值的,那么为什么没有尊重ed 命令的- 和-type 扩展变体呢?(如果遇到ed 命令,-variant 将导致编译失败,据我所知,这是一种非常合理甚至有用的语义。)e\protectcf\protectc\protect

实际上,将标记列表扩展至仅剩 TeX 基元的想法在我看来违背了旨在expl3使底层 TeX 层不可见且不可访问的理念:所有 TeX 基元都已提供expl3包装器,其中许多包装器都标有,:D表明甚至它们也不应该使用,更不用说底层基元了。

答案1

L3 扩展参数的根本动机是提供对 tex 参数扩展的方便的文档控制。偏离底层 TeX 扩展模型太远(例如不使用#1...#9作为参数)会很快使系统变慢数百倍。一般来说,定义形式缓慢并不是那么糟糕,但减慢命令的使用速度会使系统无法使用。(请记住,这种机制的大部分是在 1992 年左右实现的,但直到最近几年,它才变得足够快,可以使用。

经典的 LaTeX\protect机制通过为标记添加前缀来工作,\protect这些标记在不同的上下文中可以有不同的定义。通常,e-tex\protected机制更高效,并且会产生更易于理解的扩展路径,而无需隐藏内部\protect-前缀宏,但它的缺点是\protected宏在所有情况下都按照 e-tex 指定的方式运行,您无法像使用 那样在本地更改行为\protect。即使检测到\protected使用了宏,也需要检查\meaning检查每一个每个论点的标记。

f文档类型也是如此,它在内部使用\romannumeral扩展将初始标记扩展到第一个不可扩展标记。这就是事实,实际上没有办法将此描述抽象为不涉及“不可扩展标记”讨论的内容。让它慢几百倍,让它仍然很奇怪,但对\protected命令使用不同的行为几乎没有好处。基本上,当前的f行为对应于“tex 在顶层做什么”:如果您\protected在段落中使用命令,\protected则被忽略,命令会扩展,直到找到第一个不可扩展的标记。

f现在已基本被取代e,但请注意,e只有在 tex 引擎扩展后,它才真正有用\expanded,并e直接映射到原始行为。e在宏中实现是可能的(如果你是 Bruno),但速度慢得令人难以置信。

c类似,\protected可以设想将其定义为给出错误,\csname但事实并非如此。此类c直接映射到\csname,因此继承了该行为。您无法本地更改\protected以更改此行为,您必须对进行字符串测试\meaning每个标记进行字符串测试,因此每次使用很多在当前没有错误的情况下,速度会变得更慢,只会引发错误。

答案2

不要\protect与混淆\protected

后者是 的一个原始前缀,\def \gdef \edef \xdef并向 TeX 宣布定义的控制序列(或活动字符)应该在e-expansion 或x-expansion 中表现得就像它以 为前缀一样\noexpand

e-expansion 用于传递给\edef或 的替换文本以及或 的\xdef参数中。\expanded\write

例如,

\def\AAA{xyz}
\expanded{\noexpand\AAA}
\protected\def\BBB{xyz}
\expanded{\BBB}

将分别产生\AAA\BBB;宏扩展将稍后按照通常的规则进行。

为什么\protected重要?上面的愚蠢例子并没有告诉我们。假设你想将一些文本写出到辅助文件中以供以后使用\input。在你想要写的文本中,有一个控制序列\foo,你不希望在\write执行时展开,而希望仅在\input文件时展开。

传统的方式是

\def\foo{}% initialize
\newwrite\outstream=myauxfile
\begingroup\let\foo\relax
\write\outstream{some text with \foo}
\endgroup
\def\foo{Useful definition}
\input myauxfile

这样做是可行的,因为\relax是不可展开的,所以\foo会写成。如果你这样做,则\foo不需要\let

\protected\def\foo{}% initialize
\newwrite\outstream=myauxfile
\write\outstream{some text with \foo}
\endgroup
\def\foo{Useful definition}
\input myauxfile

\protected重新定义时也许会再次使用\foo,这取决于您实际想要做的工作)。

相反的\protect不是一个原语。它是一个控制序列,其含义根据上下文而变化。在最早版本的 LaTeX 中,你可以找到

\def\LaTeX{\protect\pLaTeX}
\def\pLaTeX{<the actual code for the logo>}

因此,如果\LaTeX在排版过程中发现 ,\protect则不会执行任何操作。但是,当在或 的\LaTeX参数(和的包装器)中发现 时, 的含义将更改为(这是基本思想,实际实现会更复杂一些),因此当执行底层或命令时,将会保留。\protected@edef\protected@write\edef\write\protect\noexpand\edef\write\pLaTeX

如今,前缀不再有明确的双重定义p,但想法保持不变。稳健的命令是\DeclareRobustCommand这样定义的:

\DeclareRobustCommand{\foo}{something}

是相同的

\expandafter\def\expandafter\foo\expandafter{%
  \expandafter\protect\csname foo \endcsname
}
\expandafter\def\csname foo \endcsname{something}

因此,与\pfoo以前的方式不同,这里\foo定义了一个命令(名称后面有一个空格)。这样,写出的文件将显示\foo(带有两个空格),但当文件为 时,这并不重要\input

如何f工作?它利用了 TeX 的一个巧妙特性(如果你不小心就会咬人): 当 TeX 寻找显式的 时<number>,它会执行宏扩展,直到找到空格标记或无法解释为在<number>当前基数下提供 的不可扩展标记。基数可以是 10、8、16 或“字母”。规则并不难:

  1. -一个明确的数字可以以任意数量的或+标记开头
  2. 后面可以跟一个基数标记,可以是反引号`、直引号'或双直引号"
    • 没有基数指定意味着十进制
    • 直引号和双直引号分别表示八进制和十六进制
    • 反引号表示“字母”常量
  3. 数字,取决于指定的基数

字母常量要么是单个字符标记,要么是长度为 1 的控制序列,例如\Q。 -expansion 使用的技巧f是,宏扩展也在字母常量之后进行,其思想是

\romannumeral-`\Q\foo

\foo在排版 的结果之前展开\romannumeral-`\Q(由于 为负数,因此结果为空)。但是,在执行此操作时,相对于<number>的状态将被忽略。因此,使用lingo,以下内容将产生不同的结果:\foo\protectedexpl3

\cs_new_protected:Nn \aad_foo: {abc}
\cs_new_protected:Nx \aad_foo_a: { \aad_foo: }
\exp_args:NNf \cs_new_protected:Nn \aad_foo_b: { \aad_foo: }

事实上,如果你尝试\cs_show:N \aad_foo_a:\ \cs_show:Naad_foo_b:` 你会分别得到

> \aad_foo_a:=\protected\long macro:->\aad_foo: .
> \aad_foo_b:=\protected\long macro:->abc.

类似的情况发生在c-expansion 中,当 TeX 尝试通过 构建控制序列时\csname。在这种情况下,显然应该忽略,因为只应保留字符标记。也许 e-TeX 的作者本可以决定在-expansion\protected上下文中,宏应该以 为前缀;但他们没有这样做。c\protected\string

相关内容