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?

4

3 Answers 3

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}
4
  • 2
    That's fantastic! I like how easily those argument types are composable by using the various \:: functions. So can I take it we'll see \::s in the next expl3 release, as you are going to secretly add it to the repo while the rest of the team is asleep? ;)
    – siracusa
    Commented May 28, 2019 at 2:08
  • 5
    @siracusa we never sleep Commented May 28, 2019 at 8:08
  • @siracusa I doubt this would be useful for more than academic purposes. For everything else there is the f expansion. Of course, if you provide an use case, a feature request could be added. It would be fun, though, if I did that ;-) Commented May 28, 2019 at 9:47
  • 1
    @DavidCarlisle “The empire on which the sun never sets” ;-) Commented May 28, 2019 at 9:58
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

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?.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .