Getting the current value of a global variable into a prop list

This is a more or less direct follow up to this question.

The problem there was to get the current value of a global variable into a LaTeX3 prop list. The trick was to use the V variant to get the value rather than to record the token list corresponding to the name of the variable.

Now the problem is the same, except instead of the value of a variable, I need the output of a calculation. I've tried playing around with Vs and xs in various places in the following MWE, but I can't get the right thing out.

\documentclass{article}
\usepackage{expl3,xparse}
\ExplSyntaxOn
\int_new:N \g_seamus_foo_int
\int_new:N \l_seamus_tmpa_int

\cs_new:Npn \seamus_set_int #1 {
\int_set:Nn \g_seamus_foo_int {#1}
}

\cs_new:Npn \seamus_modify_int #1 {
\int_set:Nn \l_seamus_tmpa_int {
\int_mod:nn {\g_seamus_foo_int + #1}{60}
}
\int_gset_eq:NN \g_seamus_foo_int \l_seamus_tmpa_int
}

\cs_new:Npn \seamus_return_int #1 {
\int_compare:nNnTF {\g_seamus_foo_int} > {#1} {Big (\int_eval:n{\g_seamus_foo_int})} {Small (\int_eval:n{\g_seamus_foo_int})}
}

\prop_new:N \g_seamus_prop_list

\cs_new:Npn \seamus_set_value #1#2 {
\prop_gput:NnV \g_seamus_prop_list {#1}{#2}
}

\cs_new:Npn \seamus_get_value #1 {
\prop_get:Nn \g_seamus_prop_list {#1}
}

\NewDocumentCommand\Marker{m}{
\seamus_set_value{#1}{
\seamus_return_int{30}
}
}

\NewDocumentCommand\ReturnMark{m}{
#1: \seamus_get_value{#1}
}
\NewDocumentCommand\SetNumber {m} {
\seamus_set_int{#1}
}

\NewDocumentCommand\ChangeNumber{m}{
\seamus_modify_int{#1}
}

\NewDocumentCommand\PrintNumber{}{
\seamus_return_int{30}
}
\ExplSyntaxOff
\begin{document}
Set at 12: \SetNumber{12}\PrintNumber\\
Add 7: \ChangeNumber{7}\PrintNumber\ (Marker foo) \Marker{foo}\\
Add 12: \ChangeNumber{12}\PrintNumber (Marker bar) \Marker{bar}\\
\ReturnMark{foo}\\
\ReturnMark{bar}

\end{document}


This is as minimal as I could make the example. The actual use case is more complex still. The main idea is that the \Marker{foo} command should save the current value of what \seamus_return_int outputs in the prop list \g_seamus_prop_list with foo as the key. So the desired output is that \ReturnMark{foo} should give "foo: Small (19)" andbar should be "bar: Big (31)".

However, \ReturnMark seems to use the value of \g_seamus_foo_int when called, rather than the value saved when \Marker was called.

I'm sure there's some expansion-fu I am not understanding...

It seems to me you want to x-type expand the value when you store it in the property list. Adding in the correct argument specs, protected status for the functions that need it, removing some unnecessary code and fixing global status leads to something like

\documentclass{article}
\usepackage{expl3,xparse}
\ExplSyntaxOn
\int_new:N \g_seamus_foo_int
\prop_new:N \g_seamus_prop

\cs_new:Npn \seamus_gset_int:n #1
{
\int_gset:Nn \g_seamus_foo_int {#1}
}

\cs_new:Npn \seamus_gmodify_int:n #1
{
\int_gset:Nn \g_seamus_foo_int
{
\int_mod:nn { \g_seamus_foo_int + #1 } { 60 }
}
}

\cs_new:Npn \seamus_return_int:n #1
{
\int_compare:nNnTF \g_seamus_foo_int > {#1}
{ Big~( \int_use:N \g_seamus_foo_int ) }
{ Small~( \int_use:N \g_seamus_foo_int ) }
}
\cs_new_protected:Npn \seamus_set_value:nn #1#2
{ \prop_gput:Nnn \g_seamus_prop {#1} {#2} }
\cs_generate_variant:Nn \seamus_set_value:nn { nx }

\cs_new:Npn \seamus_get_value:n #1
{ \prop_get:Nn \g_seamus_prop {#1} }

\NewDocumentCommand \Marker { m }
{
\seamus_set_value:nx {#1}
{ \seamus_return_int:n { 30 } }
}

\NewDocumentCommand \ReturnMark { m }
{ #1: \seamus_get_value:n {#1} }
\NewDocumentCommand \SetNumber { m }
{ \seamus_gset_int:n {#1} }

\NewDocumentCommand\ChangeNumber { m }
{ \seamus_gmodify_int:n {#1} }

\NewDocumentCommand\PrintNumber { }
{ \seamus_return_int:n { 30 } }
\ExplSyntaxOff
\begin{document}
Set at 12: \SetNumber{12}\PrintNumber\\
Add 7: \ChangeNumber{7}\PrintNumber\ (Marker foo) \Marker{foo}\\
Add 12: \ChangeNumber{12}\PrintNumber (Marker bar) \Marker{bar}\\
\ReturnMark{foo}\\
\ReturnMark{bar}
\ExplSyntaxOn
\prop_show:N \g_seamus_prop
\ExplSyntaxOff

\end{document}


Generally, if you want to force an entry to have the 'result' of something, you are likely looking for x-type expansion. To do that, you of course need to make sure that the underlying code is expandable.

The function \cs_generate_variant:Nn is used in general in expl3 programming to give control of expansion. For a function \foo:n, the arguments are used 'as is', which in the case of for example storage means that in

\int_set:Nn \l_tmpa_int { 10 }
\tl_set:Nn \l_tmpa_tl { \int_use:N \l_tmpa_int }


the token list contains the literal \int_use:N \l_tmpa_int rather than the value of the counter (10).

By creating variants we can define how arguments are expanded before passing on to the base function. Thus

\cs_generate_variant:Nn \tl_set:Nn { Nx }


allows

\tl_set:Nx \l_tmpa_tl { \int_use:N \l_tmpa_int }


where now the argument is first passed through x-type expansion before the base function \tl_set:Nn is applied. (Note: \cs_generate_variant:Nn will leave an existing variant unchanged, so is 'safe' with hand-tuned variants such as \tl_set:nx!).

As a general principle of expl3 programming, arguments should normally be expanded using variants rather than 'hard coded' approach unless carrying out low-level work (such as using delimited arguments).

The expandability of a function (macro) in expl3 should generally well-defined. Functions which are fully expandable should be defined using \cs_new:Npn or similar, while those that are not should be created using \cs_new_protected:Npn. The expl3 documents indicate which functions are expandable: if you create a new function and it only uses expandable functions then it will itself be expandable, otherwise it will not.

The reason for this approach is that it means that in a construct such as

\tl_set:Nx \l_tmpa_tl {  \foo:n { arg } }


either

1. \foo:n will be expandable and work correctly to give a 'result'
2. \foo:n will be protected and thus unchanged by the x-type expansion

Experience with LaTeX2e shows the issues with macros being partly expanded 'by accident'. (Note that ConTeXt takes a similar all-or-nothing approach.)

• I've included a quick check on the content of the property list. – Joseph Wright May 14 '14 at 13:45
• Thanks. Could you add a little explanation of why the function definition needs to be protected, and what the \cs_generate_variant does? – Seamus May 14 '14 at 14:44
• @Seamus Does the edit cover it? – Joseph Wright May 14 '14 at 14:56
• OK, I think I understand, and it seems my actual problem is a little subtler than this, since when I change (what I think is) the relevant n to an x I get a ton of Missing control sequence inserted <inserted text> \inaccessible` errors. – Seamus May 14 '14 at 15:42
• @Seamus Probably you have something non-expandable inside your real case, but as it's LaTeX2e code it's not properly protected. I'll need an example, but if it's not expandable you can't get the 'result' out. – Joseph Wright May 14 '14 at 15:45