LaTeX3:如何可靠地控制扩展级别?

LaTeX3:如何可靠地控制扩展级别?

假设你想编写 LaTeX3 代码来管理用户数据。有时你想处理这些数据,但有时你只想存储它、移动它并原封不动地返回它(例如,在开发数据结构时)。

在后一种情况下,你通常需要精确扩展到用户提供的数据,但没有进一步,因为这些数据并不总是用于排版。用户应该能够将函数存储在数据结构中,并确保返回时它们的行为仍然相同。

问题是,许多 LaTeX3 函数不能保证获得特定结果所需的扩展次数。

例如,我有一个\x包含三个带有用户数据的连续括号组的标记列表变量。我想将第三组的内容放入一个变量中\y。这是我想到的代码:

\exp_args:NNNo \exp_args:NNo \tl_set:No \y {\exp_last_unbraced:No \use_iii:nnn \x}

我发现\exp_last_unbraced:No需要 2 次扩展,并且\use_iii:nnn需要一次扩展。但这只有在这两个函数的实现悄悄地更改为需要不同数量的扩展时才会起作用。然后上面的代码可能会使用太多或太少。

我如何才能更可靠地控制这种扩展?我想这可能与家庭有关\exp_not:n,但如果有一份关于如何正确使用它们的指南,那将非常有帮助。


编辑:重新表述问题

从现有答案来看,答案当然是不是依赖于确切的扩展次数。所以我的问题最好表述如下:

是否有一种普遍推荐的“处理”数据的方法,可以更容易区分扩展直到原始水平和扩展超过原来的水平?

尝试回答

甚至在提出问题之前,我就想象它可能与打包数据有关\exp_not:n {-}。然后,如果您进行:x扩展,您将获得完全正确的数据。问题是,:f扩展、:c扩展、:v扩展等将很乐意越过这个“障碍”并吞噬数据:

\ExplSyntaxOn
    \cs_generate_variant:Nn \tl_to_str:n {x}
    \cs_generate_variant:Nn \tl_to_str:n {f}

    \tl_new:N \l_external_tl
    \tl_new:N \l_data_tl
    \tl_new:N \l_internal_tl
    \tl_set:Nn \l_external_tl  {\l_data_tl}
    \tl_set:Nn \l_data_tl      {\exp_not:n{\l_internal_tl}}
    \tl_set:Nn \l_internal_tl  {too~far}

    \noindent\tt
    \tl_to_str:x {\l_external_tl}\\  %  \l_internal_tl  %  good
    \tl_to_str:f {\l_external_tl}\\  %  too far         %  bad
\ExplSyntaxOff

所以我最近的想法是另一种“障碍”。将数据放入具有唯一 csname 的标记列表变量中。然后只将 csname 放入内部结构中。然后世界上没有任何力量可以扩展数据,直到:c使用 -related 扩展。更好的是,只需使用:v扩展即可准确获取数据,而无需进一步操作:

\ExplSyntaxOn
    \cs_generate_variant:Nn \tl_to_str:n {x}
    \cs_generate_variant:Nn \tl_to_str:n {f}
    \cs_generate_variant:Nn \tl_to_str:n {v}

    \int_zero_new:N \g__barrier_int
    \cs_new_protected:Nn \tl_set_barrier:Nn {
        \int_gincr:N \g__barrier_int
        \tl_set:cn {barrier(\int_use:N\g__barrier_int)_tl} {#2}
        \tl_set:Nx #1 {barrier(\int_use:N\g__barrier_int)_tl}
    }

    \tl_new:N \l_external_tl
    \tl_new:N \l_data_tl
    \tl_new:N \l_internal_tl
    \tl_set:Nn         \l_external_tl {\l_data_tl}
    \tl_set_barrier:Nn \l_data_tl     {\l_internal_tl}
    \tl_set:Nn         \l_internal_tl {too~far}

    \noindent\tt
    \tl_to_str:x {\l_external_tl}\\  %  barrier(1)      %  good
    \tl_to_str:f {\l_external_tl}\\  %  barrier(1)      %  good
    \tl_to_str:v {\l_external_tl}\\  %  \l_internal_tl  %  good
\ExplSyntaxOff

这样,您就可以随心所欲地堆积完全可扩展的操作,只需进行扩展:v即可获取数据。这就像指针重定向。

我可能会编写一个小包来更好地实现这一点,并将其用于我自己的 LaTeX3 程序,除非有人能给我更好的选择。请告诉我你的想法。

答案1

正如@egreg 所说,您可能使用了错误的数据类型作为起点。背后的想法expl3是完全避免在程序中使用基于扩展的编程(或更准确地说,将其限制在非常明确定义的地方)。

因此“编程原则”应该是这样的

  1. 定义您感兴趣的用户数据的外部表示形式(例如,在您的示例中为 3 个大括号组)
  2. 为想要操作的用户数据定义一个内部数据结构
  3. 定义从外部到内部的转变过程
  4. 然后仅使用内部结构进行操作,并为数据结构提供必要的变量函数(除非您使用已经具有类似工具的现有结构prop

现在显然在某些时候需要进行一些扩展,例如,用户输入可能会被扫描到某个tl变量中,您需要从那里将其取出并放入内部数据结构中,但这个步骤 3 最好发生一次,然后可以使用简单的扩展规则,例如,

   \exp_after:wN \helvens_store_user_data:nnn \x 

或者如果数据通过某些文档级参数传入

   \helvens_store_user_data:nnn #1   % with #1 being expected to be 3 brace groups

但是,除非您声明它是数据结构类型的变量,否则无论是\x还是都不#1应该长期存在并在您的代码中传递,然后您应该有一些操作函数:\x

   \helvens_retrieve_third:NN \x\y

是的,这样的功能需要做一些低级扩展(由于您选择的数据结构),但这些可以使用可用的工具来处理(依赖于某些功能所需的记录扩展),例如。

   \cs_new_protected:Npn \helvens_retrieve_third:NN #1 {
     \exp_after:wN \__helvens_retrieve_third:nnnN #1 
   }
   \cs_new:Npn \__helvens_retrieve_third:nnnN #1#2#3#4 {
      \tl_set:Nn #4{#3}
   }

和您想要/需要操作数据结构的任何其他函数类似。

但请不要这样做

   \exp_args:NNNo \exp_args:NNo \tl_set:No \y {\exp_last_unbraced:No \use_iii:nnn \x}

在编程代码的中间,以避免设置数据结构操作函数。

说了这么多...你在这里提出了一些有效的观点,我们应该

  • 更好地记录这个概念/方法
  • 记录那些合理需要设置数据结构的扩展函数,它们的扩展行为究竟是怎样的;只有一小部分应该用于此,并且大多数(并非全部)都是 1 型扩展

在其中一条评论中,有人提到,egreg 或我给出的解决方案在某些情况下“可能”不起作用。如果是这样,我建议将这些场景作为附加问题提供。我们认为这种方法应该普遍适用。

答案2

在这种情况下,我想说你使用了错误的数据类型。但是,你不需要控制需要进行多少次扩展:

\RequirePackage{expl3}
\ExplSyntaxOn
\tl_new:N \g_helvens_user_tl
\tl_gset:Nn \g_helvens_user_tl { { foo } { bar } { baz } }
\tl_new:N \l_helvens_third_tl

\cs_new_protected:Npn \helvens_get_third:NN #1 #2
 {
  \exp_last_unbraced:NNV \__helvens_get_third_aux:Nnnn #1 #2
 }

\cs_new_protected:Npn \__helvens_get_third_aux:Nnnn #1 #2 #3 #4
 {
  \tl_set:Nn #1 { #4 }
 }

\helvens_get_third:NN \l_helvens_third_tl \g_helvens_user_tl

\tl_show:N \l_helvens_third_tl

输出为

> \l_helvens_third_tl=macro:
->baz.

正如预期的那样。您很容易相信没有对标记列表中的项目进行任何扩展。

下面是一个可能更慢但我认为概念上更好的例程,它更加通用,因为它允许在标记列表中包含任意数量的项目。

\RequirePackage{expl3}
\ExplSyntaxOn
\tl_new:N \g_helvens_user_tl
\tl_gset:Nn \g_helvens_user_tl { { foo\frac } { bar\vec } { baz\sqrt } }
\tl_new:N \l_helvens_third_tl
\seq_new:N \l_helvens_temp_seq

% #1 = original token list
% #2 = new token list
% #3 = item to extract
\cs_new_protected:Npn \helvens_get:NNn #1 #2 #3
 {
  \seq_set_split:NnV \l_helvens_temp_seq { } #1
  \tl_set:Nx #2 { \seq_item:Nn \l_helvens_temp_seq { #3 } }
 }

\helvens_get:NNn \g_helvens_user_tl \l_helvens_third_tl { 3 }

\tl_show:N \l_helvens_third_tl

输出为

> \l_helvens_third_tl=macro:
->baz\sqrt .

这表明没有执行错误的扩展。

相关内容