在 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}