Suppose you have a function that operates on three tokens,

\cs_new:Nn \@@_on_three:NNN { #1, #2, #3 }

And you have a variable whose value is the three tokens:

\tl_set:Nn \l@@_three_tl { foo }

And you would like to have a variant


so that you can say

\@@_on_three:V \l@@_three_tl

However, if I'm not mistaken, a direct \cs_generate_variant would be confused by the different number of arguments (NNN vs. V or x), and in any case it will pass the expanded argument within braces, right?

So what you need is a kind of 'fake' variant, defined as:

\cs_new:Nn \@@_on_three:V
  { \exp_last_unbraced:Nx \@@_on_three:NNN #1 }

To be clear: this works. My question is rather whether there was another way to do this that is less 'naughty'---less mid-level tricking of expl3.


The definition

\cs_new:Nn \@@_on_three:V
  { \exp_last_unbraced:Nx \@@_on_three:NNN #1 }

is by the guidelines of expl3 programming 'wrong'. A V-type variant should be related to an n-type underlying function, and should not make assumptions about the content of the variable being processes. For example, what happens with

\tl_set:Nn \l_@@_three_tl { A }

with the definition of \@@_on_three:V given? Also, you are x-type expanding rather than V-type expanding, which will fail for example if the variable passed is a TeX register or if it contains something like LaTeX2e 'protected' code.

To be clear: it's not required that you use \cs_generate_variant:Nn to create variants, but it is 'required' that they behave in the same way as if they were. There are various hand-tuned variants in for example l3tl, but this is only to improve performance and does not alter behaviour. See also the fact that we've recently tightened up a lot of x-type variants as they did not show the correct outcomes in this regard.

The question does not say, but it reads as if the tl here is being using to store some structured data which will always consist of three tokens. As such, what seems to be wanted is

\cs_new:Nn \@@_on_three:N
  { \exp_last_unbraced:No \@@_on_three:NNN #1 }

where the semantics are that \@@_on_three:N should be used with a tl containing exactly three tokens. This is common is low-level work, and similar code is used for example in parts of the experimental output routine (xor) (which I'm supposed to be revising).

  • Thanks, Joseph. You're right about everything (yes, the tl is internal data, the program should choke if the variable is not 3 tokens, and the confusion between V and x is there). And your answer is exactly what I needed, just reassurance that the procedure is common (although I will adopt the :N instead of the :V as you suggest) – Federico Aug 17 '12 at 13:58
  • @Federico: depending on whether the data comes from a "trusted" source or not, you may want to add a test that the argument really is three tokens, so \cs_new:Nn \@@_on_three:N { \int_compare:nNnTF { \tl_count:N #1 } = \c_three { \exp_last_unbraced:No \@@_on_three:NNN } { \msg_error:nnx { mypkg } { mymsg } { \tl_to_str:N #1 } } } – Bruno Le Floch Aug 17 '12 at 17:41

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.