expl3 函数用于固定数量的扩展步骤

expl3 函数用于固定数量的扩展步骤

在 expl3 中,假设我们想将一个值赋给一个标记列表变量,\l_foo_tl同时将该值扩展一定次数。对于单次扩展,最简单的方法是

\tl_set:No \l_foo_tl { ... }

对于更多扩展,我们可以使用

\exp_args:NNo \tl_set:No \l_foo_tl { ... }
\exp_args:NNNo \exp_args:NNo \tl_set:No \l_foo_tl { ... }

等等。不过,这扩展性不好。对于四个扩展,我们已经必须使用\exp_args_generate:n来生成适当的扩展函数。

expl3 是否提供了一个通用的可扩展函数,一旦通过单个扩展步骤触发,该函数就会将其参数精确扩展给定的次数?如果没有,那么在 expl3 中执行此类扩展系列的最惯用方法是什么?

答案1

现在有啦!

当 LaTeX3 的人看到这个的时候他们会把我活活剥了皮 :|

这是一个s-type 扩展(s代表“责备siracusa” ;-) 与其他扩展风格不同,它接受一个参数。该参数是o标记列表的 -type 扩展的扩展数。然后您需要定义自己的\exp_args:N...命令来执行参数的扩展。例如,要将第一个参数扩展四次,您可以定义:

\cs_new:Npn \exp_args:Niv { \::s {4} \::: }

或者f扩展第一个,并将第二个扩展两次:

\cs_new:Npn \exp_args:Nft { \::f \::s {2} \::: }

或相反(第一次→两次,第二次→ f):

\cs_new:Npn \exp_args:Ntf { \::s {2} \::f \::: }

或者将某个参数扩展任意次数:

\cs_new:Npn \exp_times:nNs #1 { \::s {#1} \::: }

等等。以下是代码和一些功能证明。我定义了一个宏\a,它扩展为\b\b扩展为\c,等等,直到\f扩展为g(为了方便计算扩展):

在此处输入图片描述

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn
\cs_new:Npn \::s #1#2 \::: #3#4
  {
    \if_int_compare:w #1 > 0 ~
      \exp_after:wN \exp_after:wN
      \exp_after:wN \__siracusa_exp_step_s:nnnn
    \else:
      \exp_after:wN \__siracusa_exp_end_s:Nnnnn
    \fi:
    \exp_after:wN {#4} {#1} {#2} {#3}
  }
\cs_new:Npn \__siracusa_exp_step_s:nnnn #1#2#3#4
  {
    \exp_after:wN \::s \exp_after:wN
      { \int_value:w \__int_eval:w #2-1 \__int_eval_end: } {#3} \::: {#4} {#1}
  }
\cs_new:Npn \__siracusa_exp_end_s:Nnnnn #1#2#3#4#5
  { \__exp_arg_next:nnn {#2} {#4} {#5} }

% Examples
\cs_new:Npn \exp_times:nNs #1 { \::s {#1} \::: }
\cs_new:Npn \exp_times:nNnfso #1 { \::n \::f \::s {#1} \::o \::: }
\cs_new:Npn \weird_command:nnnn #1 #2 #3 #4
  { \tl_to_str:n {#1|#2|#3|#4} }
\ExplSyntaxOff

\def\a{\b}
\def\b{\c}
\def\c{\d}
\def\d{\e}
\def\e{\f}
\def\f{g}

\begin{document}

\ttfamily

\ExplSyntaxOn
\exp_times:nNs {0} \tl_to_str:n { \a }\par
\exp_times:nNs {1} \tl_to_str:n { \a }\par
\exp_times:nNs {2} \tl_to_str:n { \a }\par
\exp_times:nNs {3} \tl_to_str:n { \a }\par
\exp_times:nNs {4} \tl_to_str:n { \a }\par
\exp_times:nNs {5} \tl_to_str:n { \a }\par
\exp_times:nNs {6} \tl_to_str:n { \a }\par

\exp_times:nNnfso {0} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\exp_times:nNnfso {1} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\exp_times:nNnfso {2} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\exp_times:nNnfso {3} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\exp_times:nNnfso {4} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\ExplSyntaxOff

\end{document}

答案2

我可以提供一种可扩展的机制\Expandtimes,该机制处理一个参数,该参数用于保存一个⟨整数表达式⟩,该表达式的求值结果是一个值,该值表示对后续内容的第一个标记触发扩展步骤的次数。
\Expandtimes{⟨integer expression⟩}检查⟨整数表达式⟩的求值结果是否为 0。
如果是,则扩展完成。如果
不是,则\__udiez_exp_IntersperseWithExpafterwNLoop:nn在表示⟨整数表达式⟩的值减 1 的标记上启动一个循环:这些标记被一个接一个地累积起来,并\exp_after:wN在每个标记前面加上。然后\exp_after:wN\Expandtimes\exp_after:wN{将 加上并\exp_after:wN}追加。
这样,就可以构造下一次迭代的调用,其中⟨整数表达式⟩减少,并且您有一个\exp_after:wN-chain,它会在“⟨整数表达式⟩参数”后面的内容的第一个标记上触发一个扩展步骤\Expandtimes

这样,在每次迭代中,不需要将应用扩展的标记作为参数来抓取。

%\errorcontextlines=10000
\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn
%%----------------------------------------------------------------------
%% \exp:w \Expandtimes{<integer expression evaluating to value K>}<tokens>
%%     or -- as long as expl3 defines \exp:w equal to \romannumeral --
%% \romannumeral\Expandtimes{<integer expression evaluating to value K>}<tokens>
%%
%%  ->   <tokens> is hit K times by \expandafter/\exp_after:wN .
%%----------------------------------------------------------------------
\cs_new_nopar:Npn \Expandtimes #1
  { \int_compare:nNnTF { #1 }{>}{0}
                       { \exp_args:No \__udiez_exp_IntersperseWithExpafterwNLoop:nn {\int_value:w \int_eval:n{ #1-1 }}{} }
                       { \exp_end: }
  }
\cs_new_nopar:Npn \__udiez_exp_IntersperseWithExpafterwNLoop:nn #1#2 
  {  \tl_if_blank:nTF { #1 }
                      { \exp_after:wN \Expandtimes \exp_after:wN {#2\exp_after:wN} }
                      { \exp_args:No \__udiez_exp_movehead:nnn {\tl_head:w #1{} \q_stop}{#1}{#2} }  
  }
\cs_new_nopar:Npn \__udiez_exp_movehead:nnn #1#2#3 
  {  \exp_args:No \__udiez_exp_IntersperseWithExpafterwNLoop:nn {\use_i:nn{}#2}{#3\exp_after:wN#1}  }
%%----------------------------------------------------------------------
%% Argument-type based on \Expandtimes :
%%----------------------------------------------------------------------
\cs_new:Npn \::s #1#2 \::: #3#4
  {  \exp_args:No \__exp_arg_next:nnn {\exp:w \Expandtimes{#1}#4}{#2}{#3}  }
%%----------------------------------------------------------------------
%% Examples
%%----------------------------------------------------------------------
\cs_new:Npn \exp_times:nNs #1 { \::s {#1} \::: }
\cs_new:Npn \exp_times:nNnfso #1 { \::n \::f \::s {#1} \::o \::: }
\cs_new:Npn \weird_command:nnnn #1 #2 #3 #4
  { \tl_to_str:n {#1|#2|#3|#4} }
\ExplSyntaxOff

\def\a{\b}
\def\b{\c}
\def\c{\d}
\def\d{\e}
\def\e{\f}
\def\f{g}

\begin{document}

\ttfamily

\ExplSyntaxOn
\exp_times:nNs {0} \tl_to_str:n { \a }\par
\exp_times:nNs {1} \tl_to_str:n { \a }\par
\exp_times:nNs {2} \tl_to_str:n { \a }\par
\exp_times:nNs {3} \tl_to_str:n { \a }\par
\exp_times:nNs {4} \tl_to_str:n { \a }\par
\exp_times:nNs {5} \tl_to_str:n { \a }\par
\exp_times:nNs {6} \tl_to_str:n { \a }\par

\exp_times:nNnfso {0} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\exp_times:nNnfso {1} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\exp_times:nNnfso {2} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\exp_times:nNnfso {3} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\exp_times:nNnfso {4} \weird_command:nnnn{\a}{\b}{\c}{\d}\par
\ExplSyntaxOff

\end{document}

结果与以下代码相同菲利佩·奥莱尼克

在此处输入图片描述

答案3

在记起上面的帖子之前,我无法弄清楚参数交换技巧,也无法实现这样的功能这个答案.(警告:不适当的命名空间!um是 Unicode-math 的命名空间)

% Function `\__um_do_exp_after_<X in roman>:`
% absorb 2 tokens, expand the second token (absorbing later tokens in the process if required) X times
% without expanding to 2^X-1 \expandafter

% base case (1 time)
\cs_new_eq:NN \__um_do_exp_after_i: \exp_after:wN

% helper function
% define <#2> to expandafter 1 time + do <#1>.
% #1 should be __um_do_exp_after_ <X-1> : and #2 should be __um_do_exp_after_ <X> :
\cs_set:Nn \__um_exp_after_aux:NN {
  \cs_new:Npn #2 {
    \exp_after:wN #1 \exp_after:wN
  }
}

\cs_generate_variant:Nn \__um_exp_after_aux:NN {cc}

% induction case. define up to \__um_do_exp_after_x: = 10 expansions
\int_step_inline:nnn {2} {10} {
  \__um_exp_after_aux:cc {__um_do_exp_after_ \int_to_roman:n {#1-1} :} {__um_do_exp_after_ \int_to_roman:n {#1} :}
}

正如你所见,这个片段

  • 定义\__um_do_exp_after_i:=\exp_after:wN使得\__um_do_expand_after_i: <token1> <token2>扩展token2一次。
  • 定义\__um_do_exp_after_ii:=\exp_after:wN \__um_do_exp_after_i: \exp_after:wN使得\__um_do_expand_after_ii: <token1> <token2>扩展token2两次。
  • 定义\__um_do_exp_after_iii:=\exp_after:wN \__um_do_exp_after_ii: \exp_after:wN使得\__um_do_expand_after_iii: <token1> <token2>扩展token2三次。
  • ETC。

我没有测量差异,但我“感觉”这可能会更快,因为它不需要进行整数运算。


关于“交换技巧”的一些说明其他答案

  • 请注意,如果您想\myfunction {argument 1} {argument 2}直接使用来扩展第二个参数\expandafter,这将非常困难,因为您必须\expandafter在每个标记前面放置argument 1,其中可以有任意数量的标记。

  • 因此,基本思想是制作一些函数将所需参数交换为第一个参数,\expandafter照常使用,然后再交换回来。

        \myfunction {argument 1} {argument 2}
    →   \expandafter \myfunctionaux \expandafter {argument 2} {argument 1}
    →   \myfunctionaux {argument 2 expanded once} {argument 1}
    →   \myfunctionauxi {argument 1} {argument 2 expanded once}
    

也可以看看: pstricks - 我如何调用具有多个参数的宏的 \expandafter?

相关内容