LaTeX3中控制宏展开的参数说明符中有x型和e型,都表示完全展开,请问两者具体的区别是啥?比如下面这两个函数:
\use:x{<可展开的记号>}
\use:n{<可展开的记号>}
二者的区别在何处?
这两个宏都需要吃掉一个参数,并且完全展开这个参数。
第一个区别是,\use:e
(在要被完全展开的上下文中)可以被完全展开(expands all tokens fully),\use:x
不能被完全展开。
比如 \use:e { <tl> }
中 <tl>
就是“要被完全展开的上下文”,如果这里面有 \use:e
,它也会被完全展开,\use:x
则不会被展开。
例如,
% protected 宏 \__my_unexp: 在要被完全展开的上下文中不会被展开,保持原样
\cs_set_protected:Npn \__my_unexp: { do~something }
\tl_set:Nn \l__my_tl { do~other }
\use:e { \__my_unexp: \use:x { \l__my_tl } }
其结果是 \__my_unexp: \use:x { do~other }
,\__my_unexp:
和 \use:x
不会被展开,括号也不会被展开,但是 \l__my_tl
会被展开,这个展开是由 \use:e
引起的,而不是 \use:x
。
而
\use:e { \__my_unexp: \use:e { \l__my_tl } }
的结果是 \__my_unexp: do~other
,\__my_unexp:
不会被展开,但是里面的 \use:e
会被展开,它需要一个参数,即 \l__my_tl
,然后这个里面的 \use:e
展开它的参数 \l__my_tl
。
有没有办法阻止记号在要被完全展开的上下文中被展开呢?有。就是 \exp_not:N
和 \exp_not:n
(及其变体)。
\use:e { \__my_unexp: \exp_not:N \use:e { \l__my_tl } }
这样,里面的那个 \use:e
就不会被展开,但 \l__my_tl
会被外面那个 \use:e
展开,结果是 \__my_unexp: \use:e { no~other }
。
而
\use:e { \__my_unexp: \exp_not:n { \use:e { \l__my_tl } } }
结果是 \__my_unexp: \use:e { \l__my_tl }
,因为 \exp_not:n
的作用是让它的参数不被展开。
以上代码把外面的那个 \use:e
换成 \use:x
,结果完全相同。
另一个区别是,\use:x
的参数中,parameter(catcode=6),比如 #
,需要双写,但 \use:e
不需要,如:
\use:x { \cs_set:Npn \exp_not:N \__foo_do:n #1 { } }
\use:e { \cs_set:Npn \exp_not:N \__foo_do:n #1 { } }
第一行会报错。需要写成 ##1
。
除此之外,它们的作用完全相同。
l3kernel 昨天(2023-05-17,版本为 Released 2023-05-15)的更新中,要求引擎必须有 \expanded
primitive 了(从 TeXLive 2019 开始 pdfTeX、XeTeX、LuaTeX 都已经有这个 primitive 了),\use:e
也是使用 \expanded
实现的。
目前,有 e
变体的应当用这个变体,使用 \cs_generate_variant:Nn
时,也应生成 e
变体。
某些宏和 primitive,比如 \tl_set:Nx
、\cs_set_nopar:Npx
,它们内部使用的 primitive 已经完成了完全展开这个操作,所以不再需要使用 e
变体来展开。(当然不全是如此)
TeX(和 LaTeX)里关于“展开”的内容可以写一篇10页以上的文章了。
非常感谢!大佬太强了,简直就是一本行走的LaTeX3百科全书!
不好意思,上面那个
\use:n
应当是\use:e
,我写错了,不过雾月大佬已经猜到了我要说啥。@u10307 大佬,关于x型参数中双写#的问题,我还有疑问,假设我用具有x型展开的定义类函数定义一个函数,如下:
这时若将x所对应的参数中的#双写:
\cs_new:Npx \my_test:n #1 {##1}
,反而报错,这是为啥?x所对应的参数中#双写到底该怎么理解?@u10508
\cs_new:Npx
相当于\long\edef
。这里的x
其实是e
。定义函数时,
\edef\test#1#2{#1,#2}
,括号里的#1
#2
是引用第一、二个参数,应该把它们看成一个整体作为 1 个记号,##
也应当看成一个记号。\test
实际上是:两个参数的宏,它展开为<参数1>,<参数2>
。\edef\test#1{\def\noexpand\foo##1#1##2{##1 ##2 #1 ####1}}
,\test
是有一个参数的宏,它展开为\def\foo#1<参数1>#2{#1 #2 <参数1> ##1}
。当
#
作为引用参数的标识时,它应该仅写一次(随嵌套次数而倍增),当#
作为它自己时,应该双写。而
\use:x
实际上是\def\use:x#1{\edef\temp{#1}\temp}
,当写\use:x{?#1?}
时,\use:x
展开为\edef\temp{?#1?}\temp
,此时在定义\temp
时就会出错,因为它没有参数,但它的替换文本中却引用了参数,所以必须双写\use:x{?##1?}
,此时##
表示#
,而不是引用参数的标识。而
\use:e
则不是这样,它使用\expanded
primitive 实现,没有定义一个临时的宏,#
直接就是作为它自己。好的好的,非常感谢,这也太贴心了,大佬又来这回答了一次。