expl3 变量的处理 - 寄存器与宏

expl3 变量的处理 - 寄存器与宏

一些expl3变量被实现为 TeX 寄存器(dimskipmuskipint),而其他变量被实现为宏(tlstrclistfpbitset等)(相关:interface3.pdf部分“术语不准确性”。)

我的问题是,程序员是否应该expl3根据变量在内部是作为寄存器还是宏实现而区别对待它们。忽略这种区别是最佳实践吗?这似乎是V- 和-type 参数背后思想的一部分,因为无论变量是作为 TeX 寄存器还是宏实现,v它们都会获取变量的内容。expl3

我提供了两个例子。

示例 1:

-type参数e将彻底扩展所有内容(这将取代已弃用的x-type 参数)---这包括 TeX 宏和expl3作为宏实现的变量,但不包括expl3作为 TeX 寄存器的变量。为了提高效率,由于 TeX 寄存器不会在e-type 参数内扩展,程序员可以阻止对作为宏实现的expl3变量进行扩展(通过\exp_not:N'、\exp_not:n等),但不能阻止对作为 TeX 寄存器实现的变量进行扩展。但这在语法上可能不一致。

expl3(a)始终明确阻止 -type 参数内的变量扩展,还是(b)仅阻止作为宏实现的变量e扩展,这样更好吗?expl3

示例 2:

当测试两个以宏形式实现的变量是否相等时expl3,程序员可以使用\token_if_eq_meaning:NNTF。例如,程序员可以使用以下方法测试浮点相等

\fp_set:Nn \l_tmpa_fp { 1 }
\token_if_eq_meaning:NNTF \l_tmpa_fp \c_one_fp {<TRUE>} {<FALSE>}

这比以下方法快得多:

\fp_compare:nNnTF \l_tmpa_fp = \c_one_fp {<TRUE>} {<FALSE>}

dim但它在语法上可能与和int比较测试不一致。

这里也提到了类似的问题: https://github.com/latex3/latex3/issues/1499

或者使用序列:

\seq_set_from_clist:Nn \l_tmpa_seq { 1 , 2 , 3 , 4 , 5 }
\seq_set_from_clist:Nn \l_tmpb_seq { 1 , 2 , 3 , 4 , 5 }
\token_if_eq_meaning:NNTF \l_tmpa_seq \l_tmpb_seq {<TRUE>} {<FALSE>}

当然,这比测试每个序列是否具有相同数量的项目以及测试具有相同索引的每个项目的相等性要快得多。

是 (a) 使用更快的\token_if_eq_meaning:NNTF比较测试更好,还是 (b) 使用较慢但可能在语法上更一致的比较方法更好?

下面的 MWE 仅提供了一些代码示例:

\documentclass{article}
\usepackage{parskip}%looks nicer
\begin{document}
% \ExplSyntax shouldn't be used at the document level. This is just for demonstration.
\ExplSyntaxOn
\fp_set:Nn \l_tmpa_fp { 1 }
\token_if_eq_meaning:NNTF \l_tmpa_fp \c_one_fp {<TRUE>} {<FALSE>}
\par
\fp_compare:nNnTF \l_tmpa_fp = \c_one_fp {<TRUE>} {<FALSE>}
\par
\fp_compare:nNnTF \l_tmpa_fp = \c_pi_fp {<TRUE>} {<FALSE>}
\par
\seq_set_from_clist:Nn \l_tmpa_seq { 1 , 2 , 3 , 4 , 5 }
\seq_set_from_clist:Nn \l_tmpb_seq { 1 , 2 , 3 , 4 , 5 }
\token_if_eq_meaning:NNTF \l_tmpa_seq \l_tmpb_seq {<TRUE>} {<FALSE>}
\par
\seq_put_right:Nn \l_tmpb_seq { 6 }
\token_if_eq_meaning:NNTF \l_tmpa_seq \l_tmpb_seq {<TRUE>} {<FALSE>}
\ExplSyntaxOff
\end{document}

答案1

的实现细节expl3无法保证,API 文档描述了设计行为。因此,程序员不应该编写基于特定实现方面的代码。

例如,prop数据类型的实现方式有几种:多年前它依赖于toks,我们改用宏,最近我们扩展了实现以允许两个不同的底层结构(均基于宏但不同)。API 文档仍然有效,因为官方访问器等是可靠的。

就变量而言,记录展开tl,因此可以依赖它(事后看来,也许以受保护的宏形式实现会更好)。其他变量没有记录这样做,但由于没有一个变量被记录为受保护的函数,因此当变量出现在-(或-)类型上下文中时,我会\exp_not:N例行使用它。ex

虽然\cs_if_eq:NN(TF)or\token_if_eq_meaning:NN(TF)可用于比较两个标记,但一般来说,对于变量比较,我会使用适当的\<type>_if_eq:NN(TF)函数。这完全避免了对实现的依赖,而且也更清晰 - 请注意,根据底层实现,这可能相当于 anyway \cs_if_eq:NN(TF)

当然,有一个地方需要考虑性能 - 最明显的是在expl3其本身的实现中,但也在其他地方。但是,除了最紧密的循环之外,最好不要担心实现细节;一个更棘手的考虑是,例如何时使用“专用”结构化存储而不是 a prop- 例如,看看我如何在 中存储单位和数字siunitx。(单位在 中prop,数字在 中保存的专用结构中tl。)

有一个东西肯定很慢,那就是regex模块:在适当的地方,分隔函数的速度总是会快上数千倍。

答案2

这些例子有缺陷,抱歉。与\fp_compare:nNnTF你比较两个fp 表达式,而不仅仅是两个 fp 变量。

可以使用 来快速测试两个 fp 变量是否相等\token_if_eq_meaning:NNTF,并调用\fp_if_eq:NNTF;但这有多大用处?通常,fp 变量会比较“大小”,相等只是一种可能的情况。

添加序列的示例,您的代码应该具有

\prg_new_conditional:Nnn \usertwothreefour_fp_if_eq_variables:NN { p,T,F,TF }
 {
  \token_if_eq_meaning:NNTF #1 #2 { \prg_return_true: } { \prg_return_false: }
 }
\prg_new_conditional:Nnn \usertwothreefour_seq_if_eq:NN { p,T,F,TF }
 {
  \token_if_eq_meaning:NNTF #1 #2 { \prg_return_true: } { \prg_return_false: }
 }

并使用

\usertwothreefour_fp_if_eq_variables:NN \l_tmpa_fp \l_tmpb_fp { true } { false }

\usertwothreefour_seq_if_eq:NNTF \l_tmpa_seq \l_tmpb_seq { true } { false }

当然,这只是一个框架:真正的测试还应该检查后面的两个标记首先使用 fp 变量或序列。这很容易做到。但由于它需要访问内部结构,最好让团队添加该功能。

任何基于低级实现的东西都应该对程序员隐藏。即使您的功能请求未被接受,您仍应继续使用\usertwothreefour_seq_if_eq:NNTF,这样当序列的低级实现发生变化时,您只需更改其实现即可。

答案3

在“1.1 命名函数和变量”一节中,在 interface3.pdf 第 2 页,发布于 2024-03-14,您将了解变量:

V 和 v

这意味着变量值。 这说明符用于获取变量的内容,而无需担心包含数据的底层 TeX 结构。参数V 将是单个标记(类似于N),例如\foo:V \MyVariable;另一方面,使用vcsname 先构造,然后恢复值,例如\foo:v {MyVariable}

在该部分的结尾,在 interface3.pdf 第 4 页, 你学:

变量的命名方式与函数类似,但以单个字母开头来定义变量的类型:

c 常量:全局参数,其值不应改变。
g 其值只能全局设置的参数。
l 其值只能在本地设置的参数。

然后,每个变量名的构建方式与函数的构建方式类似,通常以模块1名称开头,然后是描述部分。变量以短标识符结尾,以显示变量类型:

bitset 一组位(由一系列通过位置访问的 0 和 1 标记组成的字符串)。
clist 逗号分隔的列表。
dim “刚性”长度。
fp 浮点值;
int 整数值计数寄存器。
muskip 用于数学的“橡胶”长度。
skip “橡胶”长度。
str 字符串变量:包含字符数据。
tl 标记列表变量:标记列表的占位符。

支持对上述类型之一的变量应用‑type 或‑type 扩展,但不支持对以下变量类型应用V‑type 或‑type 扩展:v

bool 或真或假。
box 盒子登记。
coffin “带手柄的盒子”——用于执行盒子对齐操作的高级数据类型。
flag 可扩展递增的非负整数。
fparray 固定大小的浮点值数组。
intarray 固定大小的整数数组。
ior⁠/​iow 输入或输出流,分别用于读取或写入。
prop 属性列表:其他语言中的字典或关联数组的类似物。
regex 正则表达式。
seq “序列”:用于实现列表(两端均可访问)和堆栈的数据类型。

1如果在数据类型模块中定义了通用暂存寄存器,则不使用模块名称,例如,int 模块包含一些暂存变量,称为\l_tmpa_int\l_tmpb_int等等。在这种情况下,在前面添加模块名称来表示模块,在后面添加模块名称来指示类型,因为这样\l_int_tmpa_int会非常难以阅读。

在“5.3 介绍变体”一节中,在 interface3.pdf 第 34 页, 你发现:

V 类型返回寄存器的值,该寄存器可以是tlclistintskip、或内置 TeX 寄存器之一。该 类型相同,只是它首先从其参数中创建一个控制序列,然后再返回值dimmuskipv

此答案最初发布的作者提出了一些挑剔的言论:

  • 与第 1.1 节不同,在第 5.3 节中,类型bitsetfpstr在支持 V 类型扩展⁠/​v 类型扩展的类型列表中没有被额外提及。也许这些信息是矛盾的。也许这些信息并不矛盾,但这些类型是根据内置 TeX 寄存器实现的,因此在第 5.3 节中额外提及它们是过时的。谁知道不看 source3.pdf 中提供的实现细节呢?新手会理解这些吗?
  • 术语的含义还取决于使用它们的上下文。(否则,例如,运算符重载之类的概念在任何情况下都意味着歧义。)术语“变量类型”的含义似乎在 LaTeX3 变量的上下文中并没有真正明确:
    一方面,建议您“以单个字母开头”(cgl)“来定义类型变量”。
    [附带问题:你真的定义是这样的吗?你真的定义变量的类型?或者您可能只是在声明变量时指定已经引入的类型?(作为一个在定义理论方面更倾向于伊曼纽尔康德的人,这个答案的最初版本的作者与 TeX 术语中遇到的“定义理论”有点不一致。)]
    另一方面,你被告知“变量以一个短标识符结尾,以显示变量 类型“。
    那么变量类型到底是什么?单个字母(cgl)是否单独表示变量的“类型”?变量的“类型”是否由bitset⁠/clist​ ⁠ /dim​ ⁠/fp​ ⁠​ ⁠int /​ ⁠/​ ⁠ /​ ⁠/​ ⁠ /​ ⁠/​ ⁠/​ ⁠/​ ⁠/​ ⁠/​ ⁠​ ⁠/​ ⁠/​ ⁠​ ⁠/​ ⁠/​ ⁠ /​ ⁠/​ ⁠​ ⁠/​ -thingie表示?您是否需要两者混合来确定/表示变量的“类型”?muskipskipstrtlboolboxcoffinflagfparrayintarrayioriowpropregexseq
  • 您会发现术语“橡胶长度”。当这个答案的最初版本的作者天真地开始学习和使用 LaTeX 时——在他大学的第一个学期,他被敦促同时有效地使用和学习 LaTeX,在他看来,这不是处理问题的好方法;先学习,然后有效地使用——,在一些介绍中看到这个术语后,在其他方面也有误导性,他在新闻组 comp.text.tex 上提问时使用了术语“橡胶长度”一两次。当时,新闻组的长期常客批评了术语“橡胶长度”的使用,他们被认为是 TeX 编程和 TeX 术语领域的杰出人物。这种批评是有道理的:“橡胶长度”不是官方的 TeX 术语,因为它是在 Donald E. Knuth 的 TeXbook 中引入和解释的术语。对于这个答案的最初版本的作者来说,橡皮是一种或多或少具有柔韧性的口香糖,用于擦除用铅笔或钢笔从纸上写的东西,并随着时间的推移而用完。 TeX 中所谓的“胶水”概念与擦除东西无关。它也与灵活性无关。它是关于描述在生产盒子的阶段“固定”事物/“固定”水平和/或垂直距离的标准。
  • “数学用途”这个术语似乎没有明确说明。这个答案的最初版本的作者假设这个术语是关于在某种数学模式下使用 TeX 排版。这个名称muskip表明这是关于数学公式中的间距和 Donald E. Knuth 的 TeXbook 中引入的 ⟨muglue⟩ 概念。

毕竟,现在让我们集中讨论这个问题:

我的问题是,程序员是否应该根据 expl3 变量在内部是作为寄存器还是宏实现来区别对待它们。

此答案的初始版本的作者倾向于根据 expl3 变量是否属于支持应用V‑type‑expansion⁠/v​ ‑type扩展的类型以及是否可以使用宏\⁠⟨⁠type⁠⟩⁠_⁠use⁠:⁠N⁠/\⁠⟨⁠type⁠⟩⁠_⁠use⁠:⁠c​ 来区别对待它们。 V‑type 或v‑type 扩展以及应用相应的\⁠⟨⁠type⁠⟩⁠_⁠use⁠:⁠N‑macro 或\⁠⟨⁠type⁠⟩⁠_⁠use⁠:⁠c‑macro 都可以与一段代码相结合,而这段代码又将所需的扩展应用于形成相关变量值的标记。

如果您希望表示 LaTeX3 变量的控制序列在某些扩展上下文中保持原样,则只需使用\exp_not:N(或可能\exp_not:c) 或\exp_not:n。请注意,的效果可能与扩展上下文中\exp_not:N的的效果不同。\exp_not:nf

例如,参见控制台输出

\ExplSyntaxOn
\cs_generate_variant:Nn \tl_to_str:n { f }
\tl_new:N \l_my_tl
\tl_set:Nn \l_my_tl {Hello}
\tex_message:D{^^J\tl_to_str:f{\exp_not:n{\l_my_tl}}}
\tex_message:D{^^J\tl_to_str:f{\exp_not:N \l_my_tl}}
\stop

,即:

Hello 
\l_my_tl 

如果变量类型不支持V‑type‑expansion⁠/v​ ‑type extension 且宏\⁠⟨⁠type⁠⟩⁠_⁠use⁠:⁠N\⁠⟨⁠type⁠⟩⁠_⁠use⁠:⁠c/​ 不可用,这是因为此类变量没有单一值(可视为“该变量的值”),而是用于存储多条数据。对于此类变量类型,建议使用提供的“官方接口”来检索构成变量值组成部分的数据。建议使用官方接口是因为此类数据结构的内部实现细节可能会发生变化。

前面关于在检索变量值的上下文中处理扩展的评论只适用于与值扩展相关的变量,这些变量可能是一个相关方面。
例如,对于像 这样的类型的变量boxcoffin其值由已经排版到框中的材料组成,因此扩展并不是一个大问题。

相关内容