f 型展开

f 型展开

我仍然很难理解 f 型展开。这到底是什么?第 2 页的解释interface3.pdf确实不太令人满意。

在给定的例子中

\tl_set:Nn \l_mya_tl { A }
\tl_set:Nn \l_myb_tl { B }
\tl_set:Nf \l_mya_tl { \l_mya_tl \l_myb_tl }

,如何检查的内容\l_mya_tl实际上是A\l_myb_tl

\l_mya_tl为了在第三行进行设置而重新使用,而不是另一个迄今为止未使用的标记列表变量,这是否重要\l_myc_tl

\l_mya_tl既然可以扩张,为何扩张完就停止呢?

是否存在可以想象的场景,即在扩展第一个标记(\l_mya_tl此处为 )后,f 扩展会继续?如何才能\l_mya_tl不中断进一步的扩展?

当预期参数真正完全展开时,为什么有人会想要使用在某个不可预测的地方停止的 f 展开?(这就是f“完全”对我的意义。)

答案1

f一旦找到不可扩展的标记,类型扩展就会结束;如果该标记是空格(字符代码 32,类别代码 10),它将被吞噬。

\tl_set:Nf \l_mya_tl { \l_mya_tl\l_myb_tl }将首先对 进行递归扩展\l_mya_tl,从而得到A。这是不可扩展的,因此业务在此停止。要分配的令牌列表被评估为A\l_myb_tl\l_mya_tl更新为包含此列表。

改变的内容\l_myb_tl也会改变的扩展\l_mya_tl,因为这个包含一个指针\l_myb_tl,而不是这个变量在定义时所具有的值。

如果你想冻结更新后的\l_mya_tl变量的值的值为\l_mya_tl,并且\l_myb_tl您必须使用x-type 或e-type 扩展。

最后两种类型导致相同的结果,但有很大区别:e-type 扩展可以出现在扩展上下文中,而x-type 不能。在这种情况下区别不大,因为你正在执行赋值。实际上,没有预定\tl_set:Ne义函数,因为事实证明\tl_set:Ne需要的时间是 的两倍\tl_set:Nx

\documentclass{article}
\usepackage{expl3,l3benchmark}

\ExplSyntaxOn

\tl_set:Nn \l_tmpa_tl { A }
\tl_set:Nn \l_tmpb_tl { B }
\tl_new:N \l_tmpc_tl
\cs_generate_variant:Nn \tl_set:Nn { Ne }

\benchmark:n { \tl_set:Nx \l_tmpc_tl { \l_tmpa_tl \l_tmpb_tl } }

\benchmark:n { \tl_set:Ne \l_tmpc_tl { \l_tmpa_tl \l_tmpb_tl } }

\stop

在我的机器上,

3.16e-7 seconds (1.01 ops)
7.78e-7 seconds (2.39 ops)

无论哪种情况,\l_tmpc_tl都被分配AB

为什么有人想要f-expansion?好问题!直到几个月前,还没有办法在扩展上下文中进行完全递归扩展。当该原语\expanded被添加到所有引擎时,情况发生了变化(以前只允许在 LuaTeX 中使用),当然,除了 Knuth TeX。

答案2

与 x 扩展进行比较:

\documentclass{article}
\usepackage{expl3}

\begin{document}
\ExplSyntaxOn
\tl_set:Nn \l_mya_tl { A }
\tl_set:Nn \l_myb_tl { B }
\tl_set:Nf \l_myc_tl { \l_mya_tl STOP \l_myb_tl }
\tl_show:N \l_myc_tl 

\tl_set:Nx \l_myc_tl { \l_mya_tl STOP \l_myb_tl }
\tl_show:N \l_myc_tl

\ExplSyntaxOff\end{document}

这将使

> \l_myc_tl=ASTOP\l_myb_tl .
<recently read> }

l.207 \tl_show:N \l_myc_tl

? 
> \l_myc_tl=ASTOPB.
<recently read> }

l.210 \tl_show:N \l_myc_tl

答案3

正如在其他答案和评论中指出的那样,f-expansion 是使用来实现的,在原语可用\romannumeral之前,有时在扩展上下文中需要它。\expanded这个答案还提到了两个可能仍然有用的用例,即没有已知终点的扩展和下一个不可扩展标记的前瞻。

此外,我想指出一个常见的用例,它甚至错误的使用它,因为它会产生不理想的结果。这是基于这样的事实:虽然x-expansion 继续完全扩展第一个不可扩展的标记之外的标记,但-expansion在标记流中使用f时更为急切。\exp_not:n

如果我们看以下例子,我们会看到当使用\exp:not:N( ) 时扩展是相同的:\noexpand

\cs_set:Npn \foo { [FOO] }

\tl_set:Nx \l_tmpb_tl { \exp_not:N \foo bar }
\tl_show:N \l_tmpb_tl

\tl_set:Nf \l_tmpb_tl { \exp_not:N \foo bar }
\tl_show:N \l_tmpb_tl

输出

> \l_tmpb_tl=\foo bar.

> \l_tmpb_tl=\foo bar.

另一方面,使用\exp_not:n( \unexpanded) 会产生不同的结果:

\tl_set:Nx \l_tmpb_tl { \exp_not:n { \foo } bar }
\tl_show:N \l_tmpb_tl

\tl_set:Nf \l_tmpb_tl { \exp_not:n { \foo } bar }
\tl_show:N \l_tmpb_tl

输出

> \l_tmpb_tl=\foo bar.

> \l_tmpb_tl=[FOO]bar.

\tl_head:当通过、\tl_tail:等函数处理标记列表变量的部分内容时,这一点尤其重要\tl_range:。所有这些函数都将其结果包装在\exp_not:n.f扩展中,在这里似乎很合适,但实际上并非如此:

\tl_set:Nn \l_tmpa_tl { \foo bar }
\tl_set:Nx \l_tmpb_tl { \tl_head:V \l_tmpa_tl }
\tl_show:N \l_tmpb_tl

\tl_set:Nf \l_tmpb_tl { \tl_head:V \l_tmpa_tl }
\tl_show:N \l_tmpb_tl

输出

> \l_tmpb_tl=\foo .

> \l_tmpb_tl=[FOO].

正如 Phelype Oleinik 指出的那样,受保护的宏的行为也不同:

\cs_new_protected:Npn \protected_foo { \foo }

\tl_set:Nx \l_tmpb_tl { \protected_foo bar }
\tl_show:N \l_tmpb_tl

\tl_set:Nf \l_tmpb_tl { \protected_foo bar }
\tl_show:N \l_tmpb_tl

输出

> \l_tmpb_tl=\protected_foo bar.

> \l_tmpb_tl=[FOO]bar.

相关内容