expl3 变量和函数

expl3 变量和函数

我正在尝试学习如何使用expl3,但有点困惑,需要一些指导。

我正在尝试弄清楚文档中提到的变量和函数的整个命名方案expl3,但无法弄清楚。有很多文字专门讨论名称空间,但实际上有全局变量和函数吗?或者它只是使用严格命名方案的人为创造。

其次,我能否得到一个函数示例,该函数可以执行以下操作,而无需使用除 之外的任何额外包expl3?或者这超出了范围expl3。我试图从中得到一种本质上以 ac 或 java 类似的方式进行编程的方法。

void myexamplefunction(int count, string[] line){
    for(int k = 0; k < count; k = k + 1){
        print k;
        print line[k];
    }
}

该函数接受一个整数count和一个名为的字符串数组line。然后它将使用 for 循环迭代 k 从 0 到 count,打印数字 k(当前迭代次数),然后打印与 k 关联的字符串。

非常感谢。

答案1

在其他语言(例如 perl 或 java)或您可能喜欢的大多数其他编程语言中,没有您想象的函数或变量。LaTeX 的工作原理是将控制序列扩展为标记,然后进一步扩展这些标记。从表面上看,它可能看起来像是变量和函数,但事实并非如此。

LaTeX 初学者也许可以适度无害地从函数和变量的角度进行思考。但从长远来看,这种思维方式会让你感到沮丧,并妨碍你编写代码来实现你想要的目标。

您需要记住 LaTeX(以及最终的 TeX)的设计目的:格式化文本。因此,它实际上没有数据结构。Expl3 的优点在于它创造了一种可以选择各种数据结构的感觉(如果它创造了这种感觉,那与实际拥有它们有什么不同吗?)。尽管如此,Expl3 提供了各种用于管理和操作文本内容的工具,但本质上它仍然与 LaTeX/TeX 处于同一个世界,您处理的是标记及其内容。最终,如果您要用 LaTeX 编写代码,您必须了解幕后发生了什么。

所以当我们写

\newcommand\mycommand[1]{do something with `#1'}

\mycommand实际的作用是将其扩展为位于其定义括号之间的任何内容。当我们使用它时,它只是扩展为:

\mycommand{stuff}->do something with `stuff'

一些编写得非常好的软件包的许多功能(双关语)都是pgf利用这种扩展过程,因此表面上代码很容易编写。但在底层,还有很多事情要做。

同样,当您使用 Expl3 时,事情也没有什么不同。Expl3 提供经过精心定义的控制序列,使其看起来以特定方式运行。

\tl_new:N \l_ae_variable_tl

看起来像是变量声明,但它只是为后面的

\tl_set:Nn \l_ae_variable_tl {content}

我认为这个想法是,在某个时候会内置一些花哨的东西\tl_set:Nn来确保接下来的控制序列已经准备好了。但目前,这实际上只不过是

\let\l_ae_variable_tl

然后后来

\def\l_ae_variable_tl{content}

\tl_new:N我可能在是否本质上允许的细节上是错误的\relax,但我的观点是它仍然按照与 LaTeX/TeX 相同的原理运行。

这是创建所需功能的方法。我采用的方法与 Manual 略有不同(我认为他的使用\int_step_inline:nnnn更简洁,也许更清晰)

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn

\int_new:N \l_ae_count_value_int

\cs_new_protected:Nn \__ae_space: { ~ }

\cs_new_protected:Npn \myexamplefunction #1#2 {
  \int_set:Nn \l_ae_count_value_int {0}
  \noindent
  \tl_set:Nn \l_ae_string_tl {#2}
  %% make sure that spaces in the user passed string don't
  %% get obliterated by the inline map
  \tl_replace_all:Nnn \l_ae_string_tl {~}{\__ae_space:}
  \tl_map_inline:Nn \l_ae_string_tl
    {
      \int_use:N \l_ae_count_value_int
      ##1
      \newline
      \int_incr:N \l_ae_count_value_int
      \int_compare:nT
        { \l_ae_count_value_int > #1 }  
        { \tl_map_break: }
    }
}

\ExplSyntaxOff

\begin{document}

\myexamplefunction{5}{this is my string}

\end{document}

答案2

首先要记住的是,它expl3是一种最终用 TeX 基元编写的语言,这意味着它是一种宏扩展语言。虽然它expl3提供了各种抽象,并尽可能地旨在“按照自己的方式”理解/记录,但这还不完整有时需要了解一些“原始”的 TeX。当仍有一些事情需要解决时尤其如此,而复杂的数据结构就是这样一个领域。


在变量范围方面,TeXexpl3基于显式的开始/结束组标记进行工作

\tl_set:Nn  \l_tmpa_tl { abc }
\tl_gset:Nn \g_tmpa_tl { abc }
\group_begin:
  \tl_set:Nn  \l_tmpa_tl { 123 }
  \tl_gset:Nn \g_tmpa_tl { 123 }  
\group_end:
\tl_show:N \l_tmpa_tl % abc
\tl_show:N \g_tmpa_tl % 123

此处命名的差异是惯例之一(例如 \tl_gset:Nn \l_tmpa_tl将会起作用,至少除非设置了额外的检查)。原因惯例是 TeX 必须跟踪变量的本地值,而随意混合本地/全局变量将导致保存堆栈耗尽(“坏消息”)。设置变量的方法仅有的本地或仅有的从整体上有助于避免这种情况。

请注意,\tl_new:N即使对于局部变量,名称也是全局分配的。该团队确实探索了可以在本地声明变量的替代方法,但这些方法似乎效果不佳,因为 TeX 作用域基于显式组。


在处理请求的“函数转换”之前,值得注意的是当前可用的相关数据结构。最基本的数据结构是标记列表(tl):一堆没有定义内部结构的标记(TeXexpl3处理标记)。我们确实有一个字符串数据类型,它是 的一种特殊形式,其中tl所有字符都是“其他”,除了空格仍然是普通空格。目前还不清楚,但我认为你实际上并不想要一个 TeX 字符串。

在此基础上,tl我们有序列(seq)和属性列表(prop)。Aseq是一种有序数据类型,适合映射,但也可以通过数字方式访问(从 1 开始索引,原因我认为最好放在单独的问题中)。Aprop是一种键值数据结构,没有任何定义的键顺序。因此,目前没有一般意义上的“数组”。要理解其中的一些内容,需要对实现有一定的了解,也需要了解它们被实现的原因。

目前,seqprop结构都是用单个 中包含的数据构建的tl。这部分是由于历史原因(过去的 TeX 系统在 csname 方面比现在受到更多限制),但部分是因为这种方法使某些用例更容易。复制单个tl很容易(一步到位),并且实现映射也很简单。另一方面,这种方法会使随机访问速度稍慢,尤其是在可扩展的情况下。如 如何在 TeX 中实现(低级)数组,在 TeX 中创建类似数组的结构的最快方法是使用一系列名称并依靠哈希表进行访问。但这会使映射等操作变得更加困难,并且最终可能会使名称存储更容易耗尽。

团队已经进行了各种讨论和尝试,重点是创建新的数据类型。然而,这些都没有取得进展,因为我们意识到需要涵盖一些可扩展形式(例如,在数组中嵌套数组,ETC。)。同时,当前的数据格式也存在,因为它们对团队来说有具体的用例。用例用于 TeX/排版是这里最重要的。(人们可以用 TeX 实现各种各样的东西,但这不一定是个好主意!)最终的结果是,目前还没有一个单一的“数组”数据结构可以完成所有可能需要的事情。

正如其他答案所指出的,人们可以使用各种公式来实现请求。例如,尚不清楚数据的其他用途,是否需要可扩展性,列表是否可能很大(性能考虑),ETC。一种可能的方法是

\documentclass{article}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn
\seq_new:N \l__bob_tmp_seq
\int_new:N \l__bob_tmp_int
\cs_new_protected:Npn \bob_print:nn #1#2
  {
    \group_begin:
      \seq_set_split:Nnn \l__bob_tmp_seq { \n } {#1} 
      \int_zero:N \l__bob_tmp_int
      \seq_map_inline:Nn \l__bob_tmp_seq
        {
          \int_incr:N \l__bob_tmp_int
          \int_compare:nNnT \l__bob_tmp_int > {#2}
            { \seq_map_break: }
          ##1
          \c_space_tl
          \tl_use:N \l__bob_tmp_tl
          \par
        }
    \group_end:
  }
\bob_print:nn { a \n b \n c \n d \n e } { 3 }
\ExplSyntaxOff
\end{document}

而其他方法(尤其是速度更快的方法)将采用基于哈希表的方法(代价是使用非常低级的 TeX“装扮”)。在上面,\seq_item:Nn出于性能原因,我使用了映射而不是(索引循环)。后一种方法必须在每个步骤中读取整个序列(至少在当前实现中),因此对于长序列来说效果不佳。另一方面,映射方法已经具有可用的项目,并且可以通过快速int操作跟踪位置。(使用哈希表数组,我们可以快速进行直接访问。)

答案3

我不是官方人员,但我会回答实际上存在全局变量和函数吗?答案是否定的。您唯一的灵活性是,您可以在当前组中设置一个宏,以便定义在组末尾“结束”,或者您可以全局设置它,但没有特殊行为。免责声明:非程序员在这里说话。

关于函数:

\documentclass{scrartcl}

\usepackage{expl3}

\begin{document}

\ExplSyntaxOn
\cs_new_protected:Npn \bob_examplefunction:nn #1 #2
 {
  \int_step_inline:nnnn { 0 } { 1 } {#1}
   { Iteration ~ ##1 ~ results ~ in ~ ``\tl_item:nn {#2} { ##1 + 1 }''.\par }
 }
\bob_examplefunction:nn {5} {Whatever}
\ExplSyntaxOff

\end{document}

另外,如果您需要一个接口,加载xparse

\NewDocumentCommand \printfirstnitems { m m }
 { \bob_examplecommand:nn {#1} {#2} }

进而

\printfirstnitems{5}{Whatever}

好的,所以你不需要字符串(tl= 标记列表),而需要数组,抱歉造成误解。数组的下一个元素是seq

\seq_new:N \l_bob_array_seq % you need to declare it, l_ shows it's local, `_seq` shows it's a seq
\cs_new_protected:Npn \bob_examplefunction:nn #1 #2
 {
  \seq_set_split:Nnn \l_bob_array_seq { , } {#2}
  \int_step_inline:nnnn { 0 } { 1 } {#1}
   { Iteration ~ ##1 ~ results ~ in ~ ``\seq_item:Nn \l_bob_array_seq { ##1 + 1 }''.\par }
 }

问题是空格事情毕竟是在 TeX 中,所以你会得到不需要的空格。你可以使用clist类似于 a 的类型,seq但它更面向用户使用,而且,除了其他方面之外,它会忽略逗号周围的空格(此外,在这种情况下,你可以避免声明 a,_clist尽管这可能更好)。

\cs_new_protected:Npn \bob_examplefunction:nn #1 #2
 {
  \int_step_inline:nnnn { 0 } { 1 } {#1}
   { Iteration ~ ##1 ~ results ~ in ~ ``\clist_item:nn {#2} { ##1 + 1 }''.\par }
 }

相关内容