Latex3: 声明局部变量

创建于 2017-10-18  ·  49评论  ·  资料来源: latex3/latex3

文档Expl3 包和 LaTeX3 编程 (v. 2017/09/18)在 p. 上说明。 7:

LaTeX3 编码约定是所有变量必须在使用前声明。

同样,文档The LaTeX3 Interfaces (v. 2017/09/18)在第 10:

... 与必须始终声明的变量相反

expl3不提供在本地声明变量的功能; 仅在全球范围内。 但是,仍然可以使用各种\..._set:N...函数隐式创建局部变量。

应该避免将变量不必要地转储到全局范围内,这是结构化编程的一个原则。 TeX 语言和我所知道的所有高级编程语言(包括 C、C#、Fortran、Java、Lisp、Pascal 和 Python)一样,提供了用于创建局部变量(即组)的作用域构造。 此外,幸运的是,LaTeX3 编程语言本身支持创建局部变量,尽管手册上有什么建议。

在我看来,上面引用的警告应该从手册中删除,并且应该说明变量可以并且应该尽可能在本地创建。

此外,如果确实希望鼓励一种在使用之前声明所有变量的编程风格(我个人认为这是编程风格的问题,应该留给程序员的个人品味),函数需要提供用于声明局部变量,就像有用于声明全局变量的函数一样。

expl3 feature-request

所有49条评论

目前的立场遵循了团队的一系列实验,以建立一种模式,该模式适用于支持expl3的 TeX 基本原理。 尤其要注意变量可以使用宏或寄存器来实现,并记住 TeX 分组的工作方式,这一点很重要。

当使用寄存器(_e.g._ 用于int类型)时,我们需要一个分配器来链接寄存器编号和我们用于访问的 cs。 相反,对于不需要的基于宏的存储。 因此,虽然\cs_set_eq:NN可用于生成新的tl名称(例如),但它会因使用寄存器而失败:

\int_set_eq:NN \l_undeclared_int \l_tmpa_in

会引发错误。

可以创建一个本地寄存器分配器,我们过去曾为 _e.g._ \int_local_new:N尝试过。 (有关这样的实现,请参阅etex包,或者回顾expl3历史。)团队最终决定反对这一点,因为它似乎“不适合”TeX 范围。 这里的关键是 TeX 分组不是基于声明而是基于显式组,并且局部变量存在于任何嵌套组中:

\begingroup
  \def\foo{}
  \begingroup
  \show\foo

当我们尝试局部 _creation_ 变量时,似乎存在混淆 TeX 分组如何工作的危险,并且可能会误导程序员。

因此,我们决定使用变量的“所有全局”_declaration_,但同时使用此类变量的局部和全局 _setting_( \l_... _versus \g_...约定)。 这将重要的事情本地化:价值。 因此,我们建议所有声明都在顶级

\tl_new:N \l_my_tl
\int_new:N \l_my_int

...
\cs_new_protected:Npn \my_func:nn #1#2
  {
    \group_begin:
      \tl_set:Nx \l_my_tl { \tl_lower_case:n {#1} }

迄今为止,这种模式似乎适用于expl3已用于的任务。

在 tex 中,即使是本地定义的变量在组结束后也不会完全销毁。 他们仍然使用一些字符串空间: https :

@u-fischer 好点:不是我们当时真正考虑过但值得牢记的。

在我看来,这种方法是错误的。 它违背了结构化编程的原则,也违背了能够创建局部变量的 TeX。 它还使 LaTeX3 不一致,因为某些数据类型可以容纳局部变量,而有些则不能。

我想请求您重新建立所有类型变量的本地创建。 这给了程序员一个选择:那些喜欢全局定义所有变量的人可以这样做,而那些希望定义一些全局变量和一些本地变量的人也可以这样做。

如果有注意事项,无论是内存方面还是其他方面,都可以在文档中提及。

2017年10月18日在21点17分,EvanAad [email protected]写道:

在我看来这是错误的方法,我想要求
你重新建立变量的本地创建。 这给了程序员一个
选择:那些喜欢全局定义所有变量的人可以这样做,并且
那些希望定义一些全局和一些本地的人也可以这样做。

对于基于寄存器类型的变量,您正在从
固定全局池所以名称的全局分配实际上远不止这些
自然。 鉴于我们假设 etex 所以有更多的 256 个寄存器
每种类型,分配行为可能比它更隐藏
可以用经典的 tex 但它仍然是
底层的 TeX 系统。 expl3 永远不可能是一个完全通用的编程
语言:它必须与底层的 TeX 系统一起工作。 你不应该
与 C# 相比,但与其他基于 TeX 的语言相比,尤其是与
\newcount 和朋友们。

说所有其他语言都允许变量的说法也不是真的
在局部作用域中声明,例如 fortran 只允许变量
在函数/子例程开始时声明。

以上都不意味着我们绝对不会添加本地声明
系统,但吸引通用语言并不是一个好的用例。
在 tex 排版中需要有合理的用例,其中
使用全球申报系统是个问题。

Fortran 要求在函数/子例程开始时声明变量的事实并不意味着这些变量是全局的。 如果你坚持鼓励一种必须声明所有变量的编程风格,那就这样吧,然后提供用于声明局部变量的函数,对应于声明全局变量的函数。

作用域和局部变量的基本原理是最重要的概念性的。 底层 TeX 引擎不释放与局部变量相关的一些资源这一事实不容忽视,但在我看来,这不能成为消除作用域和局部变量概念的原因。 除了强大的程序员,底层实现是无关紧要的; 对于强大的程序员,可以在文档中提供提示和警告。

吸引通用语言不是一个好的用例。

我想说,从 1960 年代到今天,几乎所有编程语言(包括 TeX)都实现了结构化编程的原则,这应该是 LaTeX3 团队应该有非常令人信服的理由去做的事情。 举证责任应该由那些希望废除普遍认为好的编程实践以及 TeX 中已经存在的东西的人承担; 而不是那些想要保存它的人。

@EvanAad令牌列表、逗号列表序列和属性列表变量实际上是 TeX 宏,但不同类型的变量不是。 让我们假设允许在本地声明一个整数变量。 这会消耗一个寄存器,并且有必要全局处理此问题,因为分配不会触及已分配的寄存器。

假设您的本地整数变量\x被分配了寄存器 100,它已经被上层的变量\y占用; 在本地定义\x的同一级别使用\y将是灾难性的。 因此需要对分配的寄存器进行全局簿记,这使得释放本地分配的寄存器非常困难。 为本地分配保留一块寄存器不是解决方案。

我并不是说它不能完成,但我认为这不值得痛苦。

你错过了 \newcount 不仅仅是本地(或全球)的事实
声明一个名称,它是将一个名称与一个外部定义的固定
资源。 只有一个计数寄存器 42 和所有名称
被声明为意味着 count42 指的是同一个寄存器,无论
名称分配是本地的或全局的。 正如我提到的与其他的比较
语言不是那么有用,但如果你想进行比较,你应该
比较分配文件流或一些这样的:你不能总是隔离
当它们与外部定义的接口连接时的局部声明
资源。

2017年10月18日在21:43,EvanAad [email protected]写道:

Fortran 要求在开始时声明变量的事实
函数/子程序并不意味着这些变量是全局的。 正如我
写道,如果你坚持鼓励所有变量的编程风格
必须声明,就这样吧,然后提供用于声明本地的函数
对应于声明全局变量的变量。

作用域和局部变量的基本原理是最重要的
概念性的。 事实上,底层的 TeX 引擎没有发布一些
与局部变量相关的资源不会被解雇,
但是,在我看来,这不能成为抹杀这个概念的原因
范围和局部变量。 除了电源编程器,
底层实现无关紧要; 对于电源程序员,提示
并且可以在文档中提供警告。


您收到此消息是因为您发表了评论。
直接回复本邮件,在GitHub上查看
https://github.com/latex3/latex3/issues/410#issuecomment-337721923或静音
线程
https://github.com/notifications/unsubscribe-auth/ABNcAimMfBDqA-e96Q7tkS-ERr5fv_2Mks5stmLqgaJpZM4P-Mpq
.

关键的一点是,正如 Joseph 和 David 所提到的,允许在本地定义int s可以(并且已经)完成。 我认为为了创建一种语言来呈现与结构化编程原则一致的一致抽象层,这值得的。

但是,说值得受苦对我来说很容易,因为我不是遭受实施 LaTeX3 语言之苦的人。 所以让我们采取一种仁慈的态度,并说它实际上不值得痛苦。 美好的。 在这种情况下,将数据类型分为两类:允许创建局部变量的和不允许创建的。 并在手册中描述这些类别。 不要说:所有变量必须在使用前声明。 说:“有两类数据类型。第一类,由cstlclist ,......容纳局部变量,第二类,由int ,...没有。” 并解释为什么存在第二类。 这样文档就实事求是了,程序员也有选择的余地。 有些程序根本不需要使用第二类数据类型,在这些情况下程序员为什么要声明全局变量?

@EvanAad我怀疑这是因为我习惯了 TeX 编程(包括 TeX90 的“传统”限制),但我目前不确定当前方法的问题是什么。 我们确实支持并确实鼓励本地分配的变量:它们非常常见并且确实是我们用于变量命名的语法的一部分( \l_... / \g_... )。 它们在全球范围内被分配/“保留”的事实不会干扰这一点。

顺便说一句,我们不想将接口与实现联系起来:例如,我知道prop数据类型至少有几个不同的实现,其中一个使用寄存器,而当前的一个使用宏。

再补充一点,压垮骆驼的稻草(在我的记忆中)本地寄存器/变量分配系统倒塌是因为不一致。 由于宏和寄存器的行为不同,您在编写时最终会出现不同的行为

\group_begin:
  \int_new_local:N \l_tmpa_int
  \int_gset:Nn \l_tmpa_int {7}
\group_end:
% `\l_tmpa_int` undefined

对比

\group_begin:
  \tl_new_local:N \l_tmpa_tl
  \tl_gset:Nn \l_tmpa_tl {7}
\group_end:
% `\l_tmpa_tl` defined as `7`

解决这个问题的唯一方法是为宏编写一个分配系统,但由于开销的原因从未认真考虑过。 (TeX 会在定义更多变量时减慢速度,因此将标记列表的数量加倍可能会显着影响性能。)

我仍然认为expl3通过将宏和寄存器抽象成看起来和感觉相同的函数做了正确的事情——如果缺点是语法不自然地支持真正的局部变量和寄存器,这已经看到由于上述原因,在 TeX 中的使用极其有限,那么从我的角度来看,这是一个可以接受的权衡。

@wspr但是你引用的例子是那些打破骆驼背的例子是程序员通过将全局分配给局部变量来滥用语言的例子。 这是在程序员身上。 当前的系统并没有解决滥用语言的问题,因为程序员仍然可以像使用全局变量一样使用\l_...变量。

...这符合结构化编程的原则。

这是一个相当大胆的声明,因为有不止一组
原则并鉴于试图将不同的原则统一在
过去,超集通常会创建大而无用的语言。

关键点不一定是可以做某事(如
在图灵完备的基础上,所有事物在那个层次上都是平等的)但是
是否可以有效和一致地完成某事等。

@wspr :你为什么像恶霸一样关闭这个问题? 什么都没有解决。

@EvanAad——只是想保持整洁。 讨论当然可以继续,如果有必要,我们将重新开放。

由于一些激烈的评论,我暂时锁定这些问题。

我认为这是一个相关的问题。 讨论的一个结果至少应该是我们在文档中更仔细地描述了为什么我们只选择全局声明。 @EvanAad我认为您不太了解 TeX 系统的局限性,但让我们退后一步,暂时忽略该问题。 您能否举一个使用假设的\int_local_new:N或您选择的具有您选择的语义的不同系统的示例(例如 10-20 行)? 这将有助于在更具体的环境中讨论讨论,我们可以列出好处/缺点。

大概我们必须等待锁解除,我不知道该怎么做。 无论如何,这次谈话中的大多数人都会睡几个小时。 (顺便说一句,我认为将#410 复制到#411 中作为一个混乱的讨论是非常合理的。)

纵观这是一个相当详细的讨论,我认为值得总结使我们走到今天的技术和社会历史。

在技​​术层面,TeX 提供了用于存储的寄存器和宏。 宏可以使用\def来“创建”,但是要按名称使用寄存器,我们需要一些分配器来将全局寄存器编号 _e.g._ \count40到某个名称,例如\mycount 。 这是从“第一天”开始提供的,普通的 TeX \newcount , _etc._ 提供了模型。 简单地说, \newcount全局分配,尽管名称如此,但不进行任何检查。 其他格式,最著名的是 LaTeX2e 和 ConTeXt,都普遍采用了这种方法。 他们还普遍采用了这样的想法,即在本地或全局范围内_分配_单个寄存器,因为这可以防止存储堆栈堆积。

TeX90 仅提供了 256 个常见类型的寄存器,因此在本地上下文中重用寄存器至关重要。 可以看到在 _e.g._ graphics中保持代码“理智”的地方,大量寄存器在组内被赋予了新名称,并且纯粹在本地使用。 使用 e-TeX,我们有 _lot_ 个寄存器,因此这种方法不太必要:我们可以为专用目的自由分配更多寄存器(和宏)。 特别是,这意味着我们不会像在多个名称下“回收”寄存器那样受到压力。

expl3的代码库已经开发了_long_ 时间,最初不需要e-TeX。 expl3的开发也主要在 LaTeX2e 的“顶部”完成,其一般原则是expl3不会污染文档名称空间,也不会改变核心 LaTeX2e 行为。

特别是,我们必须牢记 LaTeX2e 的\newcount类似于普通的:它全局分配并且不检查。 因此特别是

\def\foo{%
  \begingroup
    \newcount\localfoocnt

是“糟糕的风格”,好像\foo被多次调用,那么我们将用完永远不会释放的寄存器。

对于expl3 ,团队试图避免将记录的变量行为与宏或寄存器的实现联系起来。 (我确实注意到我们允许在没有访问器的情况下使用tl ,这最终确实依赖于它们是宏。)我们仍然使用整数、维度、_etc._ 的寄存器,因为它们可用并提供比在宏中做所有事情更好的性能和“自我终止”。 (使用 e-TeX 和各种\<thing>expr原语是可行的。)因此,我们需要一个分配器系统,并且为了保持一致,我们分配所有变量类型,而不仅仅是那些基于寄存器的变量类型. 正如@EvanAad所观察到的,当检查不活跃时,可以执行 _e.g._ \tl_set_eq:NN \l_new_tl \l_existing_tl没有错误,但这不是受支持的行为(例如,检查会标记它)。

作为expl3分配器的一部分,决定new _would_ 检查是否存在,与\newcount ,所以

\cs_new_protected:Npn \my_foo:
  {
    \group_begin:
      \tl_new:N \l_my_tl

如果重复使用\my_foo:将引发 _error_。 这推动了自普通的第一天以来标准 TeX 实践的方向:分配超出了宏。 (请注意,简单地说, \newcount\outer因此在宏中是“禁止的”。)

\tl_new:N \l_my_tl
\cs_new_protected:Npn \my_foo:
  {
    \group_begin:
      \tl_clear:N \l_my_tl

etex包在多年前引入了一个寄存器分配器,它可以进行寄存器的本地和全局分配, \loccount _versus_ \globcount , _etc。 很长一段时间以来, expl3加载了etex ,它被用来为\int_local_new:N和类似的东西提供功能。 鉴于大多数 TeX 程序员习惯于进行全局分配,这一点没有被广泛采用也就不足为奇了。 但是,该团队也担心可能出现误解。 像这样的东西

\cs_new_protected:Npn \my_foo:
  {
    \group_begin:
      \tl_local_new:N \l_my_tl

我们有一个问题,即嵌套调用\my_foo:时会发生什么,或者更可能使用一些“简单”名称(例如\l_my_tmp_tl )的地方。 基于我们希望new进行检查的一般想法,这应该是一个错误。 来自许多其他语言的程序员可能会觉得这有点奇怪。 当然,它确实符合 TeX 的范围规则。

值得注意的是,近年来对 LaTe2e 内核的更改意味着我们不再想加载etex (实际上,我们已经努力使expl3 “自包含”的)。 因此,对于那些不使用expl3任何新的本地分配器都必须编写为与 LaTeX2e 一起使用而不会“干扰”行为:可行但并非完全微不足道。

对于像\l_my_tmp_tl这样的东西,当然仍然可以进行全局分配,但是必须知道哪些\l_...变量是在当前组中本地分配的,哪些是全局分配但本地分配的。

我没有手头的测试数据,但可能值得注意的是,分配一个变量比简单地设置它(当然当检查不活动时)更工作,所以使用本地分配器会比全局分配器稍微慢一点本地任务。

因此,出于技术原因和“符合历史”的综合考虑,我们决定坚持严格的全局 _allocation_。 这不会以任何方式阻止变量的局部 _assignment,这是鼓励和非常广泛使用的。

这将我们带到了“社交”元素。 近年来,通过使零件“广泛”稳定,对expl3使用得到了有力的帮助。 在某个阶段,团队必须对东西做出决定,部分是为了人们可以使用它,部分是为了我们转移到其他任务上。 这并不妨碍我们重新审视事物,但更成熟的惯例需要一个很好的理由来改变。

在这里,目前我想我没有看到“通常”的方法有什么问题

\tl_new:N \l_my_tl
\cs_new_protected:Npn \my_foo:
  {
    \group_begin:
      \tl_clear:N \l_my_tl

至少到了需要我们返回并更改当前设置的程度。

@blefloch是的,我同意我对@wspr关闭线程的反应是不合理的,我向他和你们其他人道歉。 我觉得我被解雇了并被沉默了,我的反应是发脾气。 这是无法接受的。 对于那个很抱歉。

@josephwright感谢您的详细回复。 考虑一下这个问题后,我意识到如果我只使用\<module>_clear_new:N ,那么实际上我的所有变量都会表现得好像它们是周围组的本地变量,只要我使用\<module>_set...分配\<module>_gset... 。 此外,我认为这种做法符合 LaTeX3 使用前声明变量的约定,因此如果打开检查,我将不会被标记。

按照这种做法,我将重写@josephwright最后一个例子:

\cs_new_protected:Npn \my_foo:
{
    \group_begin:
        \tl_clear_new:N \l_my_tl

唯一不适合此模式的数据类型是cs ,但据我所知,使用\cs_set:Npn等创建这种类型的变量是合法的。 没有先声明它们,因为 LaTeX3 并没有真正将cs视为与其他数据类型相同的普通数据类型(这是一个单独的问题,我想与你们讨论,但我会离开它到另一个线程)。 使用这种方法,也可以在本地创建cs变量。 如果我想保持语法一致,我总是可以编写自己的\cs_clear_new:N包装器。

所以,就我而言,现在你可以关闭这个问题,如果你愿意, @wspr

@EvanAad\<thing>_clear_new:N ,注意声明 _is_ global 变量不存在。 所以

\cs_new_protected:Npn \my_foo:
{
    \group_begin:
        \tl_clear_new:N \l_my_tl
    \group_end:
}
\my_foo:
\tl_show:N \l_my_tl

将显示\l_my_tl已定义且为空,假设您之前未将其设置为其他内容。

我想加入@blefloch ,希望这个讨论能带来更好的文档。 特别是,我认为当你写到程序员“必须”遵循某种编码模式时,就像我在原帖中引用的句子一样,文档应该阐明这个“必须”是什么意思。 如果不遵守模式会怎样?

  1. 语言的行为是未定义的吗?
  2. 当前是否支持不遵守,但在未来版本中可能不支持?
  3. 引擎会报错吗?
  4. 引擎是否会报错,但只在检查开启时?
  5. 会不会造成一些小不便?
  6. LaTeX3 团队是否会因不满而抱怨,但不会导致错误或功能丢失?

作为 5 的示例,采用将参数规范附加到函数名称末尾的约定。 据我所知,如果不遵守此约定,唯一无法正常工作的功能是\cs_new:Nn ,但其余功能,包括 `cs_ new:Npn ' 都可以正常工作。

作为 6 的示例,采用使用\g_...\l_...标记局部和全局变量的约定。 据我所知,不遵守这个约定绝对不会有任何影响。 一切都会正常工作。

@EvanAad - 接受道歉,我很抱歉在讨论完成之前关闭了这个问题。

@josephwright——作为本地分配的一个微小优势,如果我写

\cs_new_protected:Npn \my_foo:
  {
    \group_begin:
      \tl_new_local:N \l_my_tl
      ...

然后我知道\l_my_tl不仅不受外部干扰,而且与使用_clear函数不同,我知道变量的所有痕迹都在函数的使用之外消失了。 换句话说,仅通过查看代码,我就知道它不能用作半全局变量。 (我可以通过使用\tl_if_exist_p:N来检查没有任何奇怪的事情发生。)

(必须运行,但如果有任何进一步讨论的意义,可以稍后继续。)

@wspr是的,但它基于 TeX 组范围的事实又回来了。 因此它可以在以下形式的构造中“半全局”使用

\cs_new_protected:Npn \my_foo:
  {
    \group_begin:
      \tl_new_local:N \l_my_tl
      \tl_set:Nn \l_my_tl { foo }
      \__my_foo:
     ..
  }
\cs_new_protected:Npn \__my_foo:
  {
    \group_begin:
        \tl_use:N \l_my_foo % Definition from \my_foo: => "foo"
...
  }

这至少是我们思考的一部分。

我想在我们离开之前我想强调的是,在技术层面上,有多种方法可以设置本地寄存器分配器。

@josephwright——不过我从来没有把它看作是一个令人困惑的例子; 例如,在 Matlab 中,它们区分不共享作用域的子函数和共享作用域的嵌套子函数。 所以,我的思想,一直都是,那么,为什么不\__my_foo:继承了“外”功能的范围是什么? 它与 TeX 的分组行为完全一致。

(很抱歉让讨论继续进行。这是漫长的一天。有人有兴趣继续讨论吗?)

上午 19.10.2017 嗯 09:23 schrieb Joseph Wright:

我想在我们离开之前我想强调的是
技术层面 有多种方法可以设置本地寄存器
分配器。

是的,但与图灵论点相当接近,即任何此类实现
在运行时会非常低效,因为底层引擎
正在全局管理寄存器存储。 和 expl3(在那个级别)
应该保持“合理的效率。

这就像全局和局部变量以及它们的增变器。 相当
而不是在每个函数中测试它是否正在对一个全局操作
局部变量,这个概念默认只出现在名字中,
例如 \l_... 应该是局部变量,不应使用
具有像 ..._ gset:Nn这样的全局函数,但我们没有在
运行

但是我们确实提供了一个检查模块(运行速度慢了多少倍)
这确保所有这些约定都得到实际遵守。

所以回到全局/局部变量

expl3 的标准概念是

  • 声明一个变量的名称一次(全局)——很多是因为在
    至少有些类型只有全局存储箱

  • 使用命名约定 \l_ \g_ 来表示局部和全局变量

它为您提供全局变量和本地变量,但具有以下限制

  • 你没有在其作用域的开始处声明一个局部变量,
    相反,您在那时使用 _set 或 _clear_new 而后者
    可能意味着变量的名称可能在全局出现在那个点
    存在

  • 在范围之外,变量仍然以默认值存在
    类型的值(例如 _int 为 0 等),因此您不会获得编译器
    如果您在其预期之外“错误地”引用了这样的变量,则会出错
    范围

所以基本上你唯一得不到的就是能够声明一个
局部变量,使其名称在作用域之外消失(并产生
如果在运行时外部引用,则出现某种未定义的错误
声明的范围)。

要做到这一点(这是可能的)expl3 需要维护自己的
资源池远远超出名称和全局之间的简单关联
引擎提供的池,这意味着所有访问都将被
非常明显地减慢了完成的速度——这违反了 expl3 的设计标准。

@FrankMittelbach——现在你让我好奇了; 寄存器的 etex.sty 方法真的那么低效吗? 或者你的意思是 tl 变量,在这种情况下我同意!

@FrankMittelbach在我看来,重要的是要区分并在文档中明确说明 LaTeX3 团队的最佳实践与 LaTeX3 语言规则的正式要求(语法或语义)。

您引用的两条规则,即:

  • 声明一个变量的名称一次(全局)
  • 使用命名约定\l_ \g_来表示局部和全局变量

属于 LaTeX3 团队认为可取的编码约定的范围,但它们都不受语言语法规则的约束,不遵守这些约定不会导致错误或功能损失。

换句话说,遵循这些规则是个人品味和编码风格的问题,我认为这应该在文档中明确说明。

这有点像说 50mh 的标志不是规则而是驾驶
惯例,如果司机遵守惯例,这是一个品味问题(只是
因为大多数时候不会立即检查)

如果你在 _gset 中使用 \l_ 变量,那么你仍然在 TeX 中编程,但是
你已经停止遵守 expl3 的语法规则
语。 它会立即破坏您的代码吗? 可能不是,但你
生成保存堆栈构建(参见 TeXbook 索引)

你是说如果我们在检查它只是一个语法规则
运行时间,比如说,每个星期二?

@FrankMittelbach当然,如果你确定这是语言的规则的一部分比它是如此的定义,但我的观点是,这应该被定义为语言的规则的一部分,因为它从来不检查,因为不遵守此约定本身不会导致错误或功能丢失。

这就是说“写英文时最好用右手握笔,以免弄脏墨水”的区别。 并通过了一项法律,规定必须用右手握笔书写英语。 当然,你可以通过这样的法律,并且有合理的理由来证明它是合理的,但最终应该由个人作者决定用哪只手握笔。 也会有人选择不遵守这条规则,但仍然和遵守这条规则的人一样,写得井井有条。

我认为@FrankMittelbach夸大了本地声明的开销( \loccount在本地管理计数,它所做的一切都不是全局的,因此可以保持开销良好)。

我认为提供\int_local:N\tl_local:N (= \tl_set_eq:NN #1 \c_empty_tl ) 等没有什么大问题,这与\int_zero_new:N\tl_clear_new:N非常相似但只会在本地做“新”。 这需要对寄存器进行一些工作,但不要过多。

@EvanAad你应该知道不声明变量可能会在某些时候咬你(警告:这段代码会产生无限多的页面)。

\documentclass{article}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn
\tl_put_left:cn { l_my_tl } { foobar \par }
\l_my_tl
\end{document}

@blefloch我要说的是,在宏内部声明一个变量(使用\<module>_clear_new )完全没问题,而且您不必将其命名为\l_amount_paid_int ,您可以简单地调用它\amount_paid\amountPaid ,就像您在任何其他编程语言中一样。 \l_..._int是一个很好的助记符,可以提醒您它是整数并且应该在本地使用,但您不应该被迫使用它或任何其他助记符。

因为它从来没有被检查过,而且因为不遵守这个约定
本身不会导致错误或功能丧失。

但这就是重点

a) 它确实会根据情况造成伤害——即当你混合时
对同一个变量的全局和局部赋值

b) 我们确实会根据要求进行检查(现在该代码可能不是
功能但它曾经并且可能最终会再次因为a))

ps是的,我明白你关于握笔(左撇子)的观点,是的,我
已经超过限速(在我的自行车上)但我仍然认为这是一个
交通规则不是交通惯例,是的,可以违反
没有伤害,但一个人也可能因此而死亡,或者至少最终会被罚款

(我之前的代码示例指出的是,即使对于 tl,在使用之前声明它们也是必不可少的。有一个检查选项可以测试声明,但在正常使用中我们不想要这样的开销。)

我同意@EvanAad 的观点,即名称只是一种约定。 即使没有 l_/g_ 命名约定,我们也可以检查局部和全局赋值是否没有混合:由于允许检查代码有点慢,因此存储有关给定变量是否在局部/全局中使用的信息是非常合理的任务。 检查类型的情况是类似的,除了无法区分的两对类型(我不打算说哪个是为了避免使对话脱轨)。

在“约定”中, \amountPaid是一个文档命令,而\l_amount_paid_int不是,后者是amount命名空间的一部分(按照约定,但在 TeX 中是一个非常重要的命名空间)。 这不适用于\l_ / \g_ ,尽管我认为许多语言在命名上有很强的“引导”,而没有在技术层面上强制执行。

在2017年10月19日,在16点58分,约瑟夫·赖特[email protected]写道:

在“约定”中,amountPaid 是一个文档命令,而 \l_amount_paid_int 不是,后者是数量命名空间的一部分(按照约定,但在 TeX 中是一个非常重要的命名空间)。 这不适用于\l_/\g_,尽管我认为许多语言在命名上有很强的“引导”,而没有在技术层面上强制执行。

LaTeX2e 编程中最糟糕的噩梦是定义命令
在“用户级空间”中,说 \foo,以发现它发生冲突
使用为 _internal_ 定义的另一个包的命令(所以
手册中没有记载)。

真的发生了吗? 是的,而且不止一次。 坚持
\@commandname约定非常有助于避免
这样的问题。

当然,“用户级空间”的冲突也可能发生,
但它们更容易发现和解决。 当谈到
内部结构,通常需要以非常快的速度进行扩展
深层次。

与立法者不同,我们不能罚款或监禁
谁不遵守 LaTeX3 编程的规律。 但我们是
一个社区,每个人都应该。

我们关于命名约定的指南应该有助于永远找不到
包的内部命令与其他命令发生冲突。

对于个人代码,一个人有权为所欲为:有
没有法律禁止一个人在他们的私人财产中超速,但是
有一个关于在公共道路上这样做。

如果你想在本地定义任何类型的变量 \f,
您应该担心某些人定义的命令
包,根据墨菲定律,恰好在论证中结束
到使用变量\f 的函数。 你能想象更糟的吗
设想?

再见
恩里科

并且您不必将其命名为 \l_amount_paid_int,您可以简单地将其命名为 amount_paid,就像您在任何其他编程语言中所做的那样。 \l_... 是一个很好的助记符,提醒您它应该在本地使用,但不必使用它或任何其他助记符。

当然。 你也可以使用 md5-fc693aa157832059d7daeeb61c55 cddb:paid或 amount&paid(这不是开玩笑,我知道一个使用 & 的包)或任何你喜欢的。 但即使名称只是一个约定:如果人们坚持这样的约定,它会使交流更容易。 你在 tex.sx 上问了很多问题。 如果以“个人风格”编写的代码出现问题,您会怎么做? 将其翻译为标准样式,提出问题并将其翻译回来? 还是期望每个人都能驾驭您的个人风格?

@eg9

对于个人代码,一个人有权为所欲为

通过阅读文档,您不会知道这一点,这就是我要说的。

如果你想在本地定义任何类型的变量\f ,你应该担心某个包定义的命令

我不同意。 如果您的代码在\group_begin: ... \group_end: ,并且如果您使用\<module>_clear_new:N定义所有局部变量,并且您仅使用\<module>_set:N...分配给局部变量,则无需担心您的本地变量名称与其他包发生冲突,除非您的代码使用另一个包。

@u-fischer

但即使名称只是一个约定:如果人们坚持这样的约定,它会使交流更容易。

我并不是说没有充分的理由坚持这些惯例。 我要说的是,这些约定不应该成为语言规则,并且文档应该清楚地区分约定(应说明其基本原理)和规则。 是否遵循约定的选择最终取决于程序员。

@EvanAad即使在您描述的情况下,您也确实需要担心名称冲突。 说你写

\cs_new_protected:Npn \evanaad_halve:n #1
  {
    \group_begin:
      \int_zero_new:N \f
      \int_set:Nn \f { (#1) / 2 }
      \iow_term:x { \int_use:N \f }
    \group_end:
  }

然后你的包的用户做了

 \int_const:Nn \f {123}
 \evenaad_halve:n { \f }

他们会惊讶地看到 0 而不是 62。

另一方面,如果您坚持使用诸如\evanaad_f等名称(或更短的\@@_f等使用l3docstrip魔法),您应该是安全的。

通过阅读文档,您不会知道这一点,这就是我要说的。

抱歉,expl3.pdf 使用“约定”一词大约 20 次。 在公共命令和私有命令的情况下,甚至有一句话“没有严重的计算开销,(几乎)没有办法强制执行此操作,因此我们只能通过命名约定来实现它,”。

@blefloch好点。

@u-fischer 好的,足够公平。 函数名应该以参数说明符结尾的约定怎么样? 这不完全是一个约定,因为3.3节的函数检查参数说明符,但这些函数只是“语法糖”,如果你不使用它们,使用像\mymodule_myfunc这样的函数名也没有障碍,但你不会从手册中知道它。

在2017年10月19日,在17:52,EvanAad [email protected]写道:

@u-fischer 好的,足够公平。 函数名称应该以参数说明符结尾的约定怎么样? 这不完全是一个约定,因为 3.3 节的函数检查参数说明符,但这些函数只是“语法糖”,如果你不使用它们,使用像 \mymodule_myfunc 这样的函数名称没有障碍。

当然,这对于 cs_generate_ variant:Nn是必要的。

再见
恩里科

使用\mymodule_myfunc您将无法使用任何函数
l3expan 。 这些扩展函数和变体的概念是
expl3的核心部分。

虽然我同意变量名可以更短(删除
"l_"/"g_" 和 "_int"/...),函数签名真的不是“只是一个
习俗”。

@blefloch我明白你的观点,这是一个很好的观点,但是,在我看来,如果你只想编写一个文档命令,你应该知道你可以通过定义一个函数来做到这一点,例如

\ExplSyntaxOn
\cs_new:Npn \MyDocumentCommand {Hello,~world!}
\ExplSyntaxOff

并且您不必先定义“影子”函数\mymodule_my_document_command: ,然后将其复制

\cs_new_eq:NN \MyDocumentCommand \mymodule_my_document_command:

@blefloch顺便说一句,除了@eg9提到的\cs_generate_variant:Nnl3expan模块的任何其他函数是否使用了函数名称的参数说明符部分?

现在在我看来你主要是为了争论而争论

是的,您可以完成所有这些,并且到一天结束时,这是唯一的硬语言
规则是 TeX 引擎在其原语中硬连线的内容。 并给予
TeX 是一种自我修改的语言,你基本上可以去任何地方
那里例如

\endlinechar-1\def~#1{\catcode`#113}~Q~S~U~_~V~W~J~K~L~M~N~O~@~X~Y~[~] ~(
~|~&~Z~'~"~ ~h~z~:~q~j~k~;~/~)~!~,~$~+\let_\let_~newcount~$$-1~Q ~J~V~S~K~W~U~L~,~''1~""2~ *1_&\count&144'&155'&145"&154"_[\ifnum_(\ifcase_O\or
_|\else_]\fi_N\number_@\advance_X\expandafter_Z\global_Y\typeout_~newif
~\ifG~\if_~\def~j{[0 Q[0Jk|$]|$]|$]|$]}~k{&1NQNJ}~\2#1#2{}~:#1{

11#12#13#14#15#16#17#18}~h#1#2{#2:{~\q#1}~#2^^J}~\q#1#2{(&1 #1#2~~OO$]}

~/{Y{行和列? 例如 E6}\read$toM\ifcat~X\2M~$$X\jM|!input!]}~!#1!{
Y{无效#1.}/}~\j#1#2{Q #1@Q- @J #2@J- 0;(V!move!]}~;{V0 (jS1z1z0z{$}秒
0z1z{$}S$z1z0z{$}]}~_{@,\ifodd'-]}~z#1{{\trueK#1{\falseq}}}~q{@ QS@JK [j="
\ifZk'Z_2]@V1q|[j='ZVV\ifG\if|\aftergroupq]]]]}~\,#1{Q#1:.}~.#1{J#1;[0
WWVUQLJ]]}~+#1{(#1O2O-2O0O0O0O0O-2O2]}~){'X"X"N'Y{^^J :
~^^Jh1Ah2Bh3Ch4Dh5Eh6Fh7Gh8H :~^^J}\GfalseW(W$|0]~:\,\Gtrue[0 /];k'_1][$=WY{(,Tie| 玩家 [0>,.|$]~胜 N[0>,-],].}X\dump])}~~{ })

这是一个漂亮的 TeX 文档(实际上是一个漂亮的 LaTeX 文档)
但就其代码而言,它根本不是很有用。 和
布鲁诺能够写出这样一份文件的事实并不意味着
expl3 手册(或在这种情况下是 LaTeX 手册)应该描述其中的任何内容。

LaTeX(2.09 和 2e)代码在早期有一个大问题
太多人编程它确实了解低级快捷方式
在 TeX 中使用和误用它们,因为他们确实认为它不是
有害。 因此,大量现有的 2e 包被
彼此不兼容或在使用时在某些地方有微妙的问题
在一起等或偶尔休息,因为他们绕过了一个或
用户界面(因为没有它似乎也能工作)。

您基本上是一遍又一遍地要求我们记录这一点,
即,可能违反设计原则的捷径只是
因为它们有时会起作用(或什至总是在此刻起作用)。 但是expl3
它的公约/规则主要来自于经历
编码员过去不遵守这些规则,结果一团糟
由此。 所以不,规则是经过深思熟虑的,而不仅仅是一时兴起(大多数
的时间),即使“如果你知道你在做什么,那么你
可以在特定情况下绕过其中的大部分”,但这并不意味着
如果你移动你的代码,那么从一个地方到另一个地方仍然是
情况或随着时间的推移是否需要如此。

正如其他人所说,您编写的代码之间存在很大差异
为你自己和你写的“正式”分发的代码
包裹。 至少对于后者,我们要求接受规则并
将它们视为语言的一部分。 对于你自己,你可能想要
学习如何对上述文档进行编码,但了解如何做
这不会出自 expl3 手册


话虽如此,我不想阻止你挑战概念,
命令,接口,你有什么。 你提出的许多观点
其他场合也很好(或者至少让我们重新思考了一个
或另一点)。

但就 expl3 手册而言,我认为您从
有几个人是没有兴趣记录
“非常规”和“非常规”。 此外,如果代码转移太多
从我们所说的规则/约定来看,它仍然是 TeX 代码,但是
不再是expl3代码。

我想我们已经讨论过这个问题:我会关闭,但如果需要,当然会重新开放。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

frougon picture frougon  ·  6评论

josephwright picture josephwright  ·  12评论

dbitouze picture dbitouze  ·  3评论

dbitouze picture dbitouze  ·  12评论

josephwright picture josephwright  ·  31评论