5

In expl3, say we want to assign a value to a token list variable \l_foo_tl while expanding the value a certain number of times. For a single expansion the easiest way is

\tl_set:No \l_foo_tl { ... }

For more expansions we can use

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

and so on. This doesn't scale nicely, though. For four expansions we already have to use \exp_args_generate:n to generate proper expansion functions.

Does expl3 provide a generic, expandable function that, once tiggered by a single expansion step, expands its argument a given number of times exactly? If not, what is the most idiomatic way of doing such an expansion series in expl3?

siracusa
  • 13,411

3 Answers3

9

There is now!

The LaTeX3 people will flay alive me when they see this :|

Here's an s-type expansion (s stands for “blame siracusa” for this ;-) that, unlike other expansion flavours, takes an argument. The argument is the number of expansions of o-type expansions of the token list. You need then to define your own \exp_args:N... commands that do the expansion of the argument. For example, to expand the first argument four times you can define:

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

or to f-expand the first, and expand the second two times:

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

or the contrary (first→twice, second→f):

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

or yet expand some argument an arbitrary amount of times:

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

and so on. Here's the code and some proofs-of-functionality. I define a macro \a which expands to \b, \b to \c, and so on until \f expands to g (to count expansions easily):

enter image description here

\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

I can offer an expandable mechanism \Expandtimes which processes an argument which is to hold an ⟨integer expression⟩ whose evaluation-result is to be a value which denotes how many times an expansion-step is to be triggered to the first token of subsequent stuff.
\Expandtimes{⟨integer expression⟩} checks whether the ⟨integer expression⟩ evaluates to 0.
If so, expansion is done.
If not so, a loop \__udiez_exp_IntersperseWithExpafterwNLoop:nn is started on the tokens representing the value of the ⟨integer expression⟩ decremented by 1: These tokens are accumulated one by one with \exp_after:wN prepended to each of them. Then \exp_after:wN\Expandtimes\exp_after:wN{ is prepended and \exp_after:wN} is appended.
This way the call for the next iteration is constructed where ⟨integer expression⟩ is decremented and you have an \exp_after:wN-chain which triggers one expansion step on the first token of the stuff right behind \Expandtimes' ⟨integer expression⟩-argument.

This way the tokens where expansion shall be applied to do not need to be grabbed as an argument in each iteration.

%\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}

The result is the same as with the code of Phelype Oleinik:

enter image description here

Ulrich Diez
  • 28,770
1

Before remembering about the post above, I could not figure out the argument swap trick and implement something like this for this answer. (warning: inappropriate namespacing! um is Unicode-math's namespace)

% 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} :} }

As you can tell, this snippet

  • defines \__um_do_exp_after_i: = \exp_after:wN such that \__um_do_expand_after_i: <token1> <token2> expands token2 once.
  • defines \__um_do_exp_after_ii: = \exp_after:wN \__um_do_exp_after_i: \exp_after:wN such that \__um_do_expand_after_ii: <token1> <token2> expands token2 twice.
  • defines \__um_do_exp_after_iii: = \exp_after:wN \__um_do_exp_after_ii: \exp_after:wN such that \__um_do_expand_after_iii: <token1> <token2> expands token2 three times.
  • etc.

I didn't measure the difference, but I "feel" that this could be faster because it doesn't need to do the integer arithmetic.


Some note regarding the "swap trick" in the other answer:

  • Note that if you want to expand the second argument in \myfunction {argument 1} {argument 2} directly using \expandafter, it's very hard because you have to put \expandafter before each token in argument 1 which can have an arbitrary number of tokens.

  • So the basic idea is to make some function to swap the desired argument to the first one, use \expandafter as usual, then swap back.

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

See also: pstricks - How do I have to invoke \expandafter for a macro with multiple arguments?.

user202729
  • 7,143