5

I have the following function:

\cs_new_protected:Nn \termmenu_prompt:NN
 {
  \__termmenu_write_out:N #1
  \group_begin:
  \ior_get_str:NN \c_term_ior \choice
  \tl_gset_eq:NN #2 \choice
  \group_end:
 }

The function is supposed to prompt the user to make a menu choice (where the menu is given in #1) and then locally set #2 to the value. I have it globally setting the tl right now, but I'd like to scale that back a bit for other reasons.

The reason I'm creating a group at all is to use the user-friendly \choice token to prompt with. #1 may be a truly nasty csname and certainly not something you'd want to show to an end-user. So, to safely use \choice, I'm creating a group. The trick is to get this information outside that group.

Sean Allred
  • 27,421

2 Answers2

7

Compared to many other programming languages, TeX is unusual in that we can arrange to 'smuggle' a value out of exactly one grouping level. In functional/procedural languages, grouping works differently and the closest equivalent idea is returning a value from a function (something that TeX cannot really do). As expl3 is after all just a wrapper around TeX, we are limited in what we can do to what makes sense at the TeX level.

If you are happy using a global variable (as one might do in a functional language when the value isn't a return one) then the approach suggested by yo' is entirely appropriate. On the other hand, if you want to ensure that the value you are dealing with escapes only one group then what you need to do is 'smuggle' it out. With expl3 we can do that with one of the expansion control functions

\cs_new_protected:Npn \termmenu_prompt:NN #1#2
  {
    \__termmenu_write_out:N #1
    \group_begin:
      \ior_get_str:NN \c_term_ior \choice
   \exp_args:NNNV \group_end:
   \tl_set:Nn #1 \choice
 }

The idea here that that we extract the value of \choice into the input stream, and it is then used in setting (here) the token list #2. By using \exp_args:NNNV everything is kept reasonably clear.

Smuggling a value out of a group is quite a low-level operation and therefore is dependent on TeX's expansion concept. It's difficult to suggest a better syntax (dedicated function) that would achieve the same outcome but be clearer. Either the \group_end: will be hidden or the putative function has to be exactly positioned relative to the \group_end:. One possible approach

\input expl3-generic\relax
\ExplSyntaxOn
\cs_new_protected:Npn \tl_set_after_group:Nn #1#2#3 \group_end:
  {
      #3
    \group_end:
    \tl_set:Nn #1 {#2}
  }
\cs_generate_variant:Nn \tl_set_after_group:Nn { NV }

\group_begin:
  \tl_set:Nn \l_tmpa_tl { foo }
  \tl_set_after_group:NV \l_tmpa_tl \l_tmpa_tl
  \tl_set:Nn \l_tmpa_tl { bar }
  \tl_show:N \l_tmpa_tl
\group_end:
\tl_show:N \l_tmpa_tl

Done in this way, we would need a dedicated function for each variable type (presumably with no gset versions!). One might then make some generic set up (\group_set_after:nNn { tl } \l_tmpa_tl { foo }) but this seems more awkward. If there is a real need for this type of functionality please raise on LaTeX-L! (One obvious issue here is the code is dependent on \group_end: appearing in the following tokens.)

Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
  • I the absence of supporting documentation, I'll provide this chat link to note that answer is the official way to do something like this. @yo's answer is still pretty clever, even if potentially bad (see chat) :) – Sean Allred May 23 '15 at 18:00
  • 1
    Hmmm, I remember writing a more generic wrapper to get local vars out of a group using \tex_aftergroup:D at one point. It was one of my classic "fun to write but probably bad abstraction" moments. I wonder if I'd be able to find where I put it... – Will Robertson May 26 '15 at 10:42
  • Turns out this is actually a duplicate question. My earlier code is here: http://tex.stackexchange.com/a/56319/179 – Will Robertson May 26 '15 at 12:24
4

You mean like this, using a temporary global variable to quit the group? I'm not quite sure that you actually need the group here at all, but maybe it's just because it's a MWE.

\tl_gnew:N \g_termmenu_temp_tl
\cs_new_protected:Nn \termmenu_prompt:NN
 {
  \__termmenu_write_out:N #1
  \group_begin:
  \ior_get_str:NN \c_term_ior \choice
  \tl_gset_eq:NN \g_termmenu_temp_tl \choice
  \group_end:
  \tl_set_eq:NN #2 \g_termmenu_temp_tl
 }
yo'
  • 51,322