L3 编程层对被视为“LaTeX3 错误”的变量赋值施加了许多限制,即:i)变量必须先声明才能赋值;ii)变量可以声明为“本地”或“全局”,给定变量不能接受不同于其声明范围的赋值;iii)声明变量具有全局效果,结合前面的限制,意味着不可能使用仅存在于本地的变量。
下面的文档说明了这些 L3 错误:
\documentclass{article}
\RequirePackage[enable-debug]{expl3}
\ExplSyntaxOn
\debug_on:n { check-declarations , deprecation }
\ExplSyntaxOff
\begin{document}
% A global assignment of a local variable is not allowed.
\ExplSyntaxOn
\tl_new:N \l_package_variable_tl
\tl_gset:Nn \l_package_variable_tl {global value}
\ExplSyntaxOff
% An assignment to a variable which was not previously declared is not
% allowed.
\ExplSyntaxOn
\tl_set:Nn \l_package_undeclared_var_tl {value}
\ExplSyntaxOff
% The previous restriction, however, implies we cannot rely on variables which
% exist only locally, since the variable declaration is always global.
\ExplSyntaxOn
\group_begin:
\tl_new:N \l_package_declared_var_tl
\group_end:
\tl_if_exist:NTF \l_package_declared_var_tl
{ \iow_term:n { Var~exists~outside~the~group. } }
{ \iow_term:n { Var~does~not~exist~outside~the~group. } }
\ExplSyntaxOff
\end{document}
所有这些也都已详细记录,因此 MWE 只是说明了如何考虑限制偏差check-declarations
,即“错误”。
我知道人们可以通过某种方式绕过这些限制,但在考虑这样做时,我不确定是否应该这样做。特别是,由于“我不是程序员”™,我可能无法理解为什么存在这些限制。所以这才是真正的问题:它们背后的理由是什么?
这些是技术限制吗?据我所知,这些不是 TeX 限制,但 TeX 中是否存在一些特殊之处,导致这些类型的分配出现问题?这些类型的分配本身在“概念上”或“逻辑上”是“错误的”吗?这是故意的限制,目的是“试图遏制整个生态系统中的不良编码实践”吗?总之,这些限制存在的原因是什么?
答案1
此限制的起源在 The TeXbook 第 301 页中有解释:
超出“保存大小”容量的特殊情况是最难纠正的错误之一,特别是如果你只在长作业中遇到错误时。每当 TeX 执行非全局赋值给某个数量时,而该数量的前一个值不是在同一分组级别上分配的,那么它通常会使用两个字的保存大小。如果宏编写正确,则很少需要在“保存堆栈”中放置超过 100 个左右的东西;但是如果对同一个变量同时进行局部和全局赋值,则可以使保存堆栈的使用无限制增长。你可以通过设置来确定 TeX 在保存堆栈上放置了什么
\tracingrestores=1
;然后你的日志文件将记录有关在组末尾从堆栈中删除的任何内容的信息。例如,让\a
代表命令 '\advance\day by 1
';让\g
代表 '\global\advance\day by 1
';并考虑以下命令:\day=1 {\a\g\a\g\a}
第一个
\a
设置\day=2
并记住旧值,\day=1
方法是将其放在保存堆栈上。第一个全局\g
设置\day=3
;全局分配时,无需将任何内容放在保存堆栈上。下一个\a
设置并记住保存堆栈上的\day=4
旧值。然后设置;然后设置并记住。最后,' ' 使 TeX 返回保存堆栈;如果此时,日志文件将获得以下数据:\day=3
\g
\day=5
\a
\day=6
\day=5
}
\tracingrestores=1
{restoring \day=5} {retaining \day=5} {retaining \day=5}
解释:该
\day
参数首先恢复为其全局值 5。由于此值是全局的,它将被保留,因此其他保存的值(\day=3
和\day=1
)基本上被忽略。寓意:如果您发现 TeX 保留了大量值,则您有一组宏可能会导致保存堆栈在足够大的作业中溢出。为了防止这种情况,通常明智的做法是对您使用的每个变量的赋值保持一致;赋值应该始终是全局的或始终是局部的。
当 TeX 处于分组级别中时,如果您执行本地赋值,TeX 会将变量的旧值“保存”在所谓的“保存堆栈”中,以便在分组结束时恢复它。但是,如果您在其上进行全局赋值,它将“锁定”保存堆栈中的前一个条目,然后,当您对该变量执行另一个本地赋值时,TeX 将不得不向保存堆栈添加另一个条目。重复此操作,最终会耗尽 TeX 的内存,这就是为什么 Knuth 建议在赋值范围上保持一致,以及为什么expl3
强制执行这一点。
如果您对所编写的代码有精确的控制,则可以小心地将全局和局部赋值混合到同一个变量中而不会遇到麻烦。例如,如果您在组内对全局变量进行局部赋值(以 TeXbook 中的示例为例\g{\a}\g
),或者如果您在组级别零上对全局变量进行局部赋值,则不会出现问题。