4

I'm trying to use one of LaTeX3's mapping commands \tl_map_variable:nNn as follows:

\documentclass{article}
\usepackage{expl3}

\begin{document}
\ExplSyntaxOn
\tl_new:N \l_foo_tl
\tl_set:Nn \l_foo_tl {x}

\tl_use:N \l_foo_tl
\tl_map_variable:nNn {abc} \l_foo_tl {\tl_use:N \l_foo_tl}
\tl_use:N \l_foo_tl

\ExplSyntaxOff
\end{document}

The expected output is xabcx. However, the map function seems to get stuck into an infinite loop, and I'm not sure why that happens. Even changing the token list {abc} into {} yields the same outcome.

From enabling tracing of macros in the log file, I can tell that the problem is related to \q_recursion_tail which is expanded to itself over and over again.

Could anyone explain what's wrong here?

3

\tl_map_variable:nNn does not create a group level, so at the end of the code \l_foo_tl contains the last token in the list, which is \q_recursion_tail (which is inserted after your abc to detect the end of the list). Then, when you do \tl_use:N \l_foo_tl you expand \q_recursion_tail, leading to infinite recursion.

\tl_map_variable:nNn expands once to:

\__tl_map_variable:Nnn #2 {#3} #1 \q_recursion_tail
\prg_break_point:Nn \tl_map_break: { }

and the first thing \__tl_map_variable:Nnn does is to assign the first item in #1 (which eventually ends up being \q_recursion_tail) to #2, which is your ⟨tl var⟩. You should use another ⟨tl var⟩ to avoid that:

\documentclass{article}
\usepackage{expl3}

\begin{document}
\ExplSyntaxOn
\tl_new:N \l_foo_tl
\tl_new:N \l_bar_tl
\tl_set:Nn \l_foo_tl {x}

\tl_use:N \l_foo_tl
\tl_map_variable:nNn {abc} \l_bar_tl { \tl_use:N \l_bar_tl }
\tl_use:N \l_foo_tl

\ExplSyntaxOff
\end{document}

The documentation doesn't mention (not that I could find, at least), the contents and usability of the ⟨tl var⟩ after the end of \tl_map_variable:nNn, so I don't know if this is by design or not.

However you can define a version of \tl_map_variable:nNn which saves the value of ⟨tl var⟩ and restores it when the function ends:

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn
\cs_new_protected:Npn \siracusa_tl_map_variable:nNn #1#2
  {
    \exp_args:NV
    \__siracusa_tl_map_variable:nnNn #2 {#1} #2
  }
\cs_new_protected:Npn \__siracusa_tl_map_variable:nnNn #1#2#3#4
  {
    \tl_map_variable:nNn {#2} #3 {#4}
    \tl_set:Nx #3 { \exp_not:n {#1} }
  }
\ExplSyntaxOff

\begin{document}
\ExplSyntaxOn
\tl_new:N \l_foo_tl
\tl_set:Nn \l_foo_tl {x}

\tl_use:N \l_foo_tl
\siracusa_tl_map_variable:nNn {abc} \l_foo_tl { \tl_use:N \l_foo_tl }
\tl_use:N \l_foo_tl

\ExplSyntaxOff
\end{document}
| improve this answer | |
  • 1
    Off the top of my head I think it would be better if \tl_map_variable:nNn should manually save and restore the value of #2. – Will Robertson May 27 '19 at 14:28
  • To me this behavior also seems a bit unexpected. The documentation says "The assignments to the <variable> are local." which made me think all changes inside the mapping functions do already occur inside a local group, or are at least reverted afterwards. – siracusa May 27 '19 at 15:45
  • 1
    @siracusa That sentence means that the variable used should be \l_whatever_tl (instead of \g_...) and that whatever assignment is done, it will be restored if the function is used in a group. But indeed, the behaviour is unexpected (and undocumented). There is an ongoing discussion in the l3 repository about this. – Phelype Oleinik May 27 '19 at 15:54
  • 2
    Please don't use internal functions (starting with __). They may change with no warning whatsoever. Here it's really not needed: \tl_new:N \l__siracusa_save_tl \cs_new_protected:Npn \siracusa_tl_map_variable:nNn #1#2#3 { \tl_set_eq:NN \l__siracusa_save_tl #2 \tl_map_variable:nNn {#1} #2 {#3} \tl_set_eq:NN #2 \l__siracusa_save_tl } or many other variants of that would work fine. – Bruno Le Floch May 28 '19 at 4:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.