先前雾月大佬在“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之类函数的的定义。