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?

1 Answer 1

4

Edit (by BLF): This has now been fixed: \tl_map_variable:nNn and related functions leave the variable equal to the last item in the token list, rather than to a trailing marker.

\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}
4
  • 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. Commented May 27, 2019 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
    Commented May 27, 2019 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. Commented May 27, 2019 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. Commented May 28, 2019 at 4:28

You must log in to answer this question.

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