当我使用 expl3 的递归函数时,我曾多次遇到夸克被无意扩展的情况,在这种情况下,TeX 将进入无限扩展循环。(更烦人的是,在这种情况下,我的控制台对 Ctrl+C 没有反应,所以我必须手动终止该进程。)
所以我想知道夸克是否必须以这种自膨胀的方式定义。它们似乎有两种用例:
作为具有分隔参数的各种函数的分隔符,例如
\q_recursion_stop
。在这种情况下,夸克的定义甚至不重要,因为 TeX 的扩展机制只扫描具有该名称的标记,无论它是否被定义。当比较一个 token 是否等于一个 quark 时,例如
\quark_if_recursion_tail_stop:N
有一个测试\if_meaning:w \q_recursion_tail #1 ... \fi:
\if_meaning:w
与 相同\ifx
,因此它实际上并没有扩展这里的宏,而是对替换文本中的每个标记进行内部比较。这意味着我们在这种情况下也不需要递归宏。类似这样的定义\cs_new:Nn \quark {\quark_undefined}
也可以,同时防止意外的无限循环。
有哪些情况绝对有必要采用递归夸克定义?
答案1
我第一个想到的答案是“是的,否则它们就不是夸克了”,因为这是定义,所以这就像问三角形是否必须有三条边一样。但隐含的问题是“夸克在宏定义中有用吗?”。
LaTeX 2e 和 2.09(可能更早)具有与您建议的定义类似的定义。\@nil
未定义,通常用在标记列表的末尾,并\@nnil
定义为
\def\@nnil{\@nil}
夸克被设计为此的抽象,同时解决了此设置的一些问题。
在阅读代码时,可能会有些困惑,是否应该使用
\@nil
或\@nnil
,如果你使用分隔参数,或者使用 遍历列表,则\futurelet
需要使用\@nil
,但是如果你使用 遍历列表,\def\tmp{#1}\ifx\tmp\@nnil...
则需要使用 ,\@nnil
因为最后一步将具有 ,\def\tmp{\@nil}
它不\ifx
等于\@nil
。 Quark 摆脱了这种区别,允许在所有这些上下文中使用相同的名称。要创建一个新的不同分隔符,您需要另外两个 csnames,(您建议的定义将使所有 quark ifx 相等,您需要在每种情况下都有一个唯一的未定义标记)。在那个时代,latex 严重缺少 csnames,最初许多命令都很脆弱的主要原因是定义一个受保护的 as 版本
\fragilefoo
会使\def\foo{\protect\fragilefoo}
csname 的使用量翻倍,然后 latex 根本无法适应当时的 tex 实现。
这些好处的缺点当然是紧密无限循环导致的有些残酷的错误行为。为了帮助警告不要使用“quark”这个名字,警告需要小心处理它们,可能不应该单独使用......
答案2
这个定义意味着
\tl_set:Nn \l_tmpa_tl { \q_no_value }
\quark_if_no_value:NTF \l_tmpa_tl
逻辑上为真。这使得在处理单个标记时可以快速测试夸克的存在。虽然今天可能不那么重要,但这种方法比其他方法速度更快。夸克是为处理紧密循环中的情况而设置的,这种类型的速度增益可能非常显著。
原因当然是我们正在做
\cs_set_nopar:Npn \q_no_value { \q_no_value }
\cs_set_nopar:Npn \l_tmpa_tl { \q_no_value }
\if_meaning:w \q_no_value \l_tmpa_tl % \tex_ifx:D
这是正确的:两者最终都是\long
扩展为的(非)宏\q_no_value
。
有些部分可以expl3
追溯到长的方式,当然这里也有一些“历史”。有了\pdfstrcmp
可用的,人们可以基于字符串进行许多测试:这在expl3
第一次开发时是不可能的,如果从今天开始,也许人们可能会采取不同的策略。