Say you want to write LaTeX3 code that manages user data. Sometimes you want to process that data, but other times you just want to store it, move it around and return it unchanged (when developing a data-structure, for instance).
In the latter case you often need to expand down to the user-provided data exactly, but no further, since such data is not always meant for typesetting. Users should be able to store functions inside your data-structure and be sure that when returned, they'll still behave identically.
The problem is, many LaTeX3 functions give no guarantees about the number of expansions necessary to get a specific result.
For example, I had a token list variable \x
containing three consecutive brace-groups with user-data. I wanted to put the content of the third group into a variable \y
. This is the code I came up with:
\exp_args:NNNo \exp_args:NNo \tl_set:No \y {\exp_last_unbraced:No \use_iii:nnn \x}
I found out that \exp_last_unbraced:No
requires 2 expansions and \use_iii:nnn
requires one. But this will only work until the implementation of either of those functions silently changes to require a different number of expansions. Then the above code might use one too many or one too few.
How can I get more reliable control over this kind of expansion? I imagine it might have something to do with \exp_not:n
and family, but it would be very helpful to have a guide to their proper use.
Edit: Rephrased Question
As observed by existing answers, the answer is of course not to rely on an exact number of expansions. So my question would be better phrased as:
Is there a generally recommended way of 'handling' data that makes it easier to distinguish between expansion until the original level and expansion beyond the original level?
Attempt at an answer
Even before asking the question, I imagined it might have something to do with packaging the data in \exp_not:n {-}
. Then if you do an :x
expansion, you get exactly the right data back. The problem is, :f
expansion, :c
expansion, :v
expansion, etc. will happily go past this 'barrier' and eat into the data:
\ExplSyntaxOn
\cs_generate_variant:Nn \tl_to_str:n {x}
\cs_generate_variant:Nn \tl_to_str:n {f}
\tl_new:N \l_external_tl
\tl_new:N \l_data_tl
\tl_new:N \l_internal_tl
\tl_set:Nn \l_external_tl {\l_data_tl}
\tl_set:Nn \l_data_tl {\exp_not:n{\l_internal_tl}}
\tl_set:Nn \l_internal_tl {too~far}
\noindent\tt
\tl_to_str:x {\l_external_tl}\\ % \l_internal_tl % good
\tl_to_str:f {\l_external_tl}\\ % too far % bad
\ExplSyntaxOff
So my latest idea is a different kind of 'barrier'. Put the data in a token list variable with a unique csname. And then put only the csname in your internal structures. Then no power in the world can expand the data until a :c
-related expansion is used. Even better, simply use a :v
expansion to get exactly to the data and no further:
\ExplSyntaxOn
\cs_generate_variant:Nn \tl_to_str:n {x}
\cs_generate_variant:Nn \tl_to_str:n {f}
\cs_generate_variant:Nn \tl_to_str:n {v}
\int_zero_new:N \g__barrier_int
\cs_new_protected:Nn \tl_set_barrier:Nn {
\int_gincr:N \g__barrier_int
\tl_set:cn {barrier(\int_use:N\g__barrier_int)_tl} {#2}
\tl_set:Nx #1 {barrier(\int_use:N\g__barrier_int)_tl}
}
\tl_new:N \l_external_tl
\tl_new:N \l_data_tl
\tl_new:N \l_internal_tl
\tl_set:Nn \l_external_tl {\l_data_tl}
\tl_set_barrier:Nn \l_data_tl {\l_internal_tl}
\tl_set:Nn \l_internal_tl {too~far}
\noindent\tt
\tl_to_str:x {\l_external_tl}\\ % barrier(1) % good
\tl_to_str:f {\l_external_tl}\\ % barrier(1) % good
\tl_to_str:v {\l_external_tl}\\ % \l_internal_tl % good
\ExplSyntaxOff
This way you can pile on fully-expandable operations to your hearts content and just do a :v
expansion to get to the data. It's like pointer-redirection.
I will probably write a small package to better facilitate this and use it for my own LaTeX3 programs, unless someone can give me a better option. Let me know what you think.
:D
functions), the idea is that you should really only need to know that you want either 'the stored tokens' or 'the stored value'. Even:o
expansion is really meant mainly for low-level code bootstraping the kernel itself and in places where we need performance.f
andx
expansion other than what you should expect for a variable. (c
-type expansion must give a list of tokens that can generate a name, so that case is different anyway.) You example seems very odd: can you provide a 'real world' case where the currently-available data structures/expansion controls do not work appropriately?:x
to process the operations or a:v
to be left with only the data.f
-type expansion. It's supposed to give fully-expanded tokens, so any variable use inside such an expansion should 'use' the content of the variable in some way. A standard example is forcing evaluation of an expression byf
-type expanding\int_eval:n
. Then again, without an example of what you really need to do I wonder if I miss something (I have no programming background, so am simply used to working with TeX).