根据这个答案,
所有没有星号的函数都是(或至少应该是)
\protected
,这样它们就不会扩展x
或e
键入扩展或写入文件时。
f
和c
类型扩展不同,并且忽略\protected
既然拥有尊重ed 命令的扩展类型(例如x
和)被认为是有价值的,那么为什么没有尊重ed 命令的- 和-type 扩展变体呢?(如果遇到ed 命令,-variant 将导致编译失败,据我所知,这是一种非常合理甚至有用的语义。)e
\protect
c
f
\protect
c
\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 的控制序列,例如\Q
。 -expansion 使用的技巧f
是,宏扩展也在字母常量之后进行,其思想是
\romannumeral-`\Q\foo
将\foo
在排版 的结果之前展开\romannumeral-`\Q
(由于 为负数,因此结果为空)。但是,在执行此操作时,相对于<number>
的状态将被忽略。因此,使用lingo,以下内容将产生不同的结果:\foo
\protected
expl3
\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:N
aad_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