先前雾月大佬在“LaTeX3中控制宏展开的参数说明符x和e有啥区别?”这一问题中说了x型展开和e型展开的区别,基于此我对它们的理解如下:
#
(类代码为6),必须双写它(即具有##
这样的形式);另外,参数说明符中有x
的函数本身不会在要被完全展开的环境(x-型
展开或e-型
展开)中被展开。x
不同的是:所接收的参数中若出现形参符#
(类代码为6),无需双写它;另外,参数说明符中有e
的函数在要被完全展开的环境(x-型
展开或e-型
展开)中可以被展开。对于在x型参数中双写#
这一点:
若使用如下代码(雾月大佬给的例子):
\use:x { \cs_set:Npn \exp_not:N \__foo_do:n ##1 { } }
#
的书写满足上述要求,但如果我使用如下代码:
\cs_new:Npx \my_test:n #1 {##1}
则直接报错,#
不能双写。
所以,对于x型参数中双写#
这一点该如何正确处理,我上面的理解该怎么修正?
并不是所有 x
中的 #
都需要双写。直接用 primitive 实现的一般不需要双写。但是即使这样也有例外情况,这应该是 LaTeX3 的函数命名不一致导致的,由于 \cs_new:Npx
这些函数使用广泛,目前看来不太可能再修改它们的名称了。目前我记得的不需要双写的 #
的 x
参数有 \cs_new...
和 \cs_(g)set...
系列以及 \iow_shipout_x:Nn
。所有的 \exp_..
中的 x
都需要双写(包括 \cs_generate_variant:Nn
和 \prg_generate_conditional_variant:Nnn
生成的变体)。
需要双写的原因是定义了一个临时的宏,见后文。
当 #
作为引用参数的标识时,它应该仅写一次(随嵌套次数而倍增),当 #
作为它自己时,应该双写。
需要双写的 #
可以使用 \exp_not:n
包裹起来,这样就不要双写。如可以 \use:x { \exp_not:n { \def\tmp#1{#1} } }
。
\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 \def\foo####1{####1##2}}}
,\test
是有一个参数的宏,它展开为 \def\foo#1<参数1>#2{#1 #2 <参数1> \def\foo##1{##1#2}}
。
而 \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 实现,没有定义一个临时的宏,#
直接就是作为它自己。
还是那句话,能使用 e
就使用 e
而非 x
。
谢谢雾月大佬,您解释的很细,之前是我理解岔了,我这就更新一下认知。
@u10508 最近的 expl3 的更新把一些
x
的变体改为了e
,其中就有\iow_shipout_x:...
。@u10307
\cs_new:Npx
等也改为了\cs_new:Npe
,但这些x
变体(目前)也保留了,应该是为了兼容性考虑。好的好的,非常感谢您的提醒,大佬您太贴心了!
@u10307 浏览了一遍interface3,好些函数都悄悄增加了e变体或是将x变体改为了e变体,不过这么一改也更为合理了(我也是改了一整天才把我原先记录的笔记改完,幸亏有您提醒)。大佬您早有预见,“由于
\cs_new:Npx
这些函数使用广泛,目前看来不太可能再修改它们的名称了”,内部来了一手\let
,正应了您的猜测,万不得已他们还是不敢删除\cs_new:Npx
之类函数的的定义。