Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I've just recovered from my first real foray into LaTeX3 programming. It was ... an experience. Not a completely unpleasant one and once I'd gotten used to the syntax then I found it a lot easier than it looks. I almost was able to forget that I was programming in a macro language and start thinking in terms of functions and variables again.

Almost.

Where I came up against a brick wall was in the concept of auxiliary functions. I had a main function that did a lot of work. I would quite like to have farmed off parts of it to some other functions, as much for keeping track of what was going on as anything else. But I couldn't work out how to do that separation properly.

Here's an example. Suppose I wanted to compute the Euclidean length of a lot of 2-vectors. In, say, lua then I might write:

function veclen(a,b)
  return math.sqrt(a^2 + b^2)
end

Now that isn't directly comparable with a LaTeX3 "function" so let me pretend that I can't write inline formulae in lua and write it out a bit more like LaTeX3.

function veclen(a,b)
  local s = a
  local t = b
  multiply(s,s)
  multiply(t,t)
  add(s,t)
  sqrt(s)
  return s
end

Here, multiply and add are functions that "do what they say on the tin" but, crucially, instead of returning a value they store the answer in the first variable.

That's fairly similar to my LaTeX3 function:

\cs_new:Nn \fp_veclen:NNN {
  \fp_set_eq:NN \l_hobby_veclena_fp #2
  \fp_set_eq:NN \l_hobby_veclenb_fp #3
  \fp_mul:Nn \l_hobby_veclena_fp {\l_hobby_veclena_fp}
  \fp_mul:Nn \l_hobby_veclenb_fp {\l_hobby_veclenb_fp}
  \fp_add:Nn \l_hobby_veclena_fp {\l_hobby_veclenb_fp}
  \fp_pow:Nn \l_hobby_veclena_fp {.5}
  \fp_set_eq:NN #1 \l_hobby_veclena_fp
}

The crucial difference, and what I'm asking about, is the scoping.

In the lua function, a and b are local but even if they are not then the commands local s = a and local t = b force them to be local. But the return s breaks out of the function scope and makes the answer available at the next level up. In doing this in TeX, I'd do all my computations inside a group: something like:

\cs_new:Nn \fp_veclen:NNN {
\group_begin:
  \fp_set_eq:NN \l_hobby_veclena_fp #2
  \fp_set_eq:NN \l_hobby_veclenb_fp #3
  \fp_mul:Nn \l_hobby_veclena_fp {\l_hobby_veclena_fp}
  \fp_mul:Nn \l_hobby_veclenb_fp {\l_hobby_veclenb_fp}
  \fp_add:Nn \l_hobby_veclena_fp {\l_hobby_veclenb_fp}
  \fp_pow:Nn \l_hobby_veclena_fp {.5}
\group_end:
\fp_set_eq:NN #1 \l_hobby_veclena_fp
}

Except that that wouldn't work: if I put the \group_end: where I have done so then \l_hobby_veclena_fp has lost its value. If I put it after the assignment then the assignment is local to the group and so is lost moments later.

To cut a long story short: I want the computation to be local so that I can use my temporary variables with impunity, but I need the assignment to be outside the computation group (but not global) so that it can be used by the calling code.

How do I do this? Obviously it is possible because the LaTeX3 functions must do this all the time[1]. TikZ uses "smuggling" to do this: define a temporary global variable to be the answer and then outside the group make the assignment which makes the actual assignment not global but outside the computation group: \global\let\tikz@smuggle=\the@answer\endgroup\let\the@answer=\tikz@smuggle. What's the right LaTeX3-way to do this?

For bonus points, there's another small issue with scoping. In the lua function, the variables a and b were already local: I could reassign them with impunity. In TeX, that's not so easy. If I do something like

\fp_set_eq:NN \l_my_tmpa_fp #1
\fp_set_eq:NN \l_my_tmpb_fp #2

then there's always the danger that I called my function with \calc:NN \l_my_tmpb_fp \l_my_tmpa_fp. How do I make local aliases for my incoming variables without defining a new set of temporary macros for every single function?

[1] I know I could look at the LaTeX3 code - indeed I took a quick glance and got a vague idea, but I suspect that there would be subtleties I'd miss by not asking, and I hope that others will benefit from a more public answer and explanation.

share|improve this question
    
You can use \fp_gset_eq:NN #1 \l_hobby_veclena_fp inside the group. –  Marco Daniel May 17 '12 at 19:28
1  
@MarcoDaniel But that globally sets whatever-#1-was to \l_hobby_veclena_fp. But #1 was (or could have been) only a local variable in the calling function. So it might have another global value that I don't want to touch. –  Loop Space May 17 '12 at 19:36
    
Why not use the TikZ way? Seems nice'n'easy to me... –  cgnieder May 17 '12 at 19:42
    
@cgnieder: I believe in the separation of TikZ and LaTeX3. Seriously, if there's a "proper l3 way" to do this, I'd like to know about it. –  Loop Space May 17 '12 at 19:46
3  
You can use: \edef\@tempa{\endgroup\value=\the\value\relax}\@tempa to move a value (counter, length) out of a group. Don't ask me how to translated that into Klingon, aeh, LaTeX3. The same is possible for macros: \edef\@tempa{\endgroup\def\noexpand\mymacro{\mymacro}}\@tempa or, if you don't want to expand it all the way: \def\@tempa{\endgroup\def\noexpand\mymacro{\unexpanded\expandafter{\mymacro}}}\‌​@tempa, or without e-TeX: \expandafter\endgroup\expandafter\def\expandafter\mymacro\expandafter{\mymacro}‌​. The first two values also work nicely with multiple macros/registers. –  Martin Scharrer May 17 '12 at 20:19

4 Answers 4

up vote 15 down vote accepted

Soln 1

This might be considered overkill, but I once wrote a function \group_after_set:NNn to provide an "abstraction" to handle this, used as in:

\group_begin:
  \group_begin:
    \group_after_set:NNn \int_set:Nn \y {3}
    % \y == 3
  \group_end:
  % \y == 3
\group_end:
% \y == undefined

I quite like it but I don't know if should be added to expl3 or not. Suggestions?

The advantage over this approach is that you can use it multiple times within a group to "export" multiple variables, unlike Enrico's answer which is much more simple and efficient for the common case.

Anyway, here it is:

\documentclass{article}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn
\cs_if_free:NT \group_insert_after:N
  {
    \cs_set_eq:NN \group_insert_after:N \group_execute_after:N
  }

\cs_generate_variant:Nn \tl_if_empty:nT {v}
\cs_generate_variant:Nn \tl_show:N {v}
\cs_new:Nn \group_after_set:NNn
{
  % set the variable locally for use inside the group:
  #1 #2 {#3}

  \cs_if_exist:cF { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }
    {
      \tl_new:c { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }
    }

  % first time the function is executed inside the group:
  \tl_if_empty:vT { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }
    {
      % set up the aftergroup execution:
      \group_insert_after:c
        { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }

      % reset the material for aftergroup execution:
      \tl_gset:cx { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }
        {
          \tl_gclear:c { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }
        }
    }

  % append the new material to the aftergroup execution:
  \tl_gput_right:cx
    { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }
    {
      \exp_not:n { #1 #2 } { \exp_not:V #2 }
    }
}

\cs_generate_variant:Nn \group_insert_after:N {c}

\int_new:N \y
\int_new:N \yy

\cs_generate_variant:Nn \tl_if_eq:nnF {V}
\cs_new:Nn \assert:Nn
  {
    \tl_if_eq:VnF #1 {#2} {[ERROR:\tl_to_str:n{#1~!=~#2}]}
  }

\group_begin:
  \group_after_set:NNn \tl_set:Nn \x {xx}
  \group_after_set:NNn \tl_set:Nn \xx {x}
  \assert:Nn \x {xx}
  \assert:Nn \xx {x}
  \group_begin:
    \group_after_set:NNn \int_set:Nn \y {3}
    \group_after_set:NNn \int_set:Nn \yy {2}
    \assert:Nn \y  {3}
    \assert:Nn \yy {2}
  \group_end:
  \assert:Nn \x {xx}
  \assert:Nn \xx {x}
  \assert:Nn \y  {3}
  \assert:Nn \yy {2}

  % check repeated doesn't break anything:
  \group_begin:
    \int_set:Nn \y  {6}
    \int_set:Nn \yy {5}
  \group_end:
  \assert:Nn \x {xx}
  \assert:Nn \xx {x}
  \assert:Nn \y  {3}
  \assert:Nn \yy {2}
\group_end:

\assert:Nn \x {xx}
\assert:Nn \xx {x}
\assert:Nn \y  {0}
\assert:Nn \yy {0}

\ExplSyntaxOff
\end{document}

Edit: add some comments and clean up the code slightly.

Soln 2 (added later)

From some discussion elsewhere on this page with Ahmed, it's become clear to me that the syntax introduced about isn't such a great idea. For example, if you want to set a variable using tl_set:Nx, it's not then appropriate to use that same function to define the variable outside the group. Really what we're looking for is an extension of etextool's \AfterGroup that permits easy expansion control (which is part of the whole design philosophy of expl3).

So separating the local and "group escaped" variables need to be done independently, syntax-wise. Here's a modified implementation of the above that does this. The code looks similar:

\int_new:N \y
\group_begin:
  \group_begin:
    \int_set:Nn \y {3}
    \group_var_return:NN \int_set:Nn \y
    % \y == 3
  \group_end:
  % \y == 3
\group_end:
% \y == 0

Note that since we don't have a mapping between variable types and their setting functions, in this case \int_set:Nn is required twice. This isn't so bad, really, since you could in theory use this for all sorts of setting functions. So the "general" approach here I've written as follows:

\clist_new:N \z
% ...

\group_begin:
  % let's say we're in a macro right now that processes
  % some input argument and saves the result to "\y".
  \do_something_with:Nn \y {#1}
  \group_after_insert:nV { \clist_put_right:Nn \z } { \y }
\group_end:

So it's easy to see how the former (\group_var_return:NN) is just something like \group_after_insert:nV { \int_set:Nn \y } \y. Whether these levels of abstraction are necessary or helpful is a bit of an open question. I'm inclined to think that at least the \group_var_return:NN one looks good.

Here is the implementation of all that:

\documentclass{article}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn

\cs_if_free:NT \group_insert_after:N
  {
    \cs_set_eq:NN \group_insert_after:N \group_execute_after:N
  }

\cs_generate_variant:Nn \tl_if_empty:nT {v}
\cs_generate_variant:Nn \group_insert_after:N {c}

\cs_new:Nn \group_after_insert:nn
{ 
  \cs_if_exist:cF { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }
    {
      \tl_new:c { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }
    }

  % first time the function is executed inside the group:
  \tl_if_empty:vT { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }
    {
      % set up the aftergroup execution:
      \group_insert_after:c
        { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }

      % reset the material for aftergroup execution:
      \tl_gset:cx { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }
        {
          \tl_gclear:c { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }
        }
    }

  % append the new material to the aftergroup execution:
  \tl_gput_right:cx
    { g_aftergroup_ \int_use:N \etex_currentgrouplevel:D _tl }
    {
      \exp_not:n { #1 {#2} }
    }
}

\cs_generate_variant:Nn \group_after_insert:nn {nV}

\cs_new:Npn \group_var_return:NN #1 #2
{
  \group_after_insert:nV { #1 #2  } { #2 } 
}

\int_new:N \y
\int_new:N \yy

\cs_generate_variant:Nn \tl_if_eq:nnF {V}
\cs_new:Nn \assert:Nn
  {
    \tl_if_eq:VnF #1 {#2} {[ERROR:\tl_to_str:n{#1~!=~#2}]}
  }

\group_begin:
  \tl_set:Nn \x {xx}
  \tl_set:Nn \xx {x}
  \group_after_insert:nV { \tl_set:Nn \x  } { \x  }
  \group_after_insert:nV { \tl_set:Nn \xx } { \xx }
  \assert:Nn \x {xx}
  \assert:Nn \xx {x}
  \group_begin:
    \int_set:Nn \y {3}
    \int_set:Nn \yy {2}
    \group_var_return:NN \int_set:Nn \y
    \group_var_return:NN \int_set:Nn \yy
    \assert:Nn \y  {3}
    \assert:Nn \yy {2}
  \group_end:
  \assert:Nn \x {xx}
  \assert:Nn \xx {x}
  \assert:Nn \y  {3}
  \assert:Nn \yy {2}

  % check repeated doesn't break anything:
  \group_begin:
    \int_set:Nn \y  {6}
    \int_set:Nn \yy {5}
  \group_end:
  \assert:Nn \x {xx}
  \assert:Nn \xx {x}
  \assert:Nn \y  {3}
  \assert:Nn \yy {2}
\group_end:

\assert:Nn \x {xx}
\assert:Nn \xx {x}
\assert:Nn \y  {0}
\assert:Nn \yy {0}

\ExplSyntaxOff
\end{document}
share|improve this answer
1  
I vote for implementation ;-) –  Marco Daniel May 17 '12 at 21:51
    
Obviously \group_after_set:Nn isn't the best name for the thing, though. –  Will Robertson May 17 '12 at 23:56
4  
Gets my vote, and floats my boat, and doesn't get my goat (I'll get my coat). –  Loop Space May 18 '12 at 5:36
    
@WillRobertson: If this is all about eTeX's \currentgrouptype, please have you simply used the returned number (0,16)? I am sorry, I don't do LaTeX3 at all; I am sure you know that. In the past I have had trouble getting out of deep groups (for error recovery) using \currentgrouptype. I can ask a separate question if that will help. –  Ahmed Musa May 18 '12 at 14:44
1  
I like \group_var_return:NN. Consider it stolen^H^H^H^H^H^Hcopied. –  Loop Space May 24 '12 at 7:44

You simply have to get the value of the variable before closing the group; since you have to skip three tokens, \exp_args:NNNV is what you need:

\cs_new_protected:Npn \fp_veclen:NNN #1 #2 #3
 {
  \group_begin:
  \fp_set_eq:NN \l_hobby_veclena_fp #2
  \fp_set_eq:NN \l_hobby_veclenb_fp #3
  \fp_mul:Nn \l_hobby_veclena_fp {\l_hobby_veclena_fp}
  \fp_mul:Nn \l_hobby_veclenb_fp {\l_hobby_veclenb_fp}
  \fp_add:Nn \l_hobby_veclena_fp {\l_hobby_veclenb_fp}
  \fp_pow:Nn \l_hobby_veclena_fp {.5}
  \exp_args:NNNV \group_end: \fp_set:Nn #1 \l_hobby_veclena_fp
 }

As Joseph remarks in a comment this should be protected, being not expandable; the Npn variety is more efficient.

If more variables have to be set outside the group, a different method must be used (waiting for Will's proposal to be implemented); the last line in the definition can become

\use:x
 {
  \group:end
  \fp_set:Nn \exp_not:N #1 { \l_hobby_veclena_fp }
  % other similar assignments
 }

(Thanks to Joseph Wright and Marco Daniel for remarking it.)

Note that \fp_veclen:Nnn should have this signature if you plan to call it as \fp_veclen:Nnn \l_hobby_veclen_fp {2}{3}. Also \fp_set_eq:NN should be \fp_set:Nn.

share|improve this answer
    
This should be \cs_new_protected as it's not expandable; I'd also favour \cs_new_protected:Npn. –  Joseph Wright May 17 '12 at 21:30
    
@MarcoDaniel I've added the remark; as I wrote, this can be particularly useful when more than one assignment has to be performed "outside the group". –  egreg May 18 '12 at 10:31

With the new FPU, we can avoid the group entirely in this particular case (for the more general 'escape from a group' question, see the answer by egreg).

\cs_new_protected:Npn \fp_veclen:NNN #1#2#3
 {
   \fp_set:Nn #1
     {
        ( ( #2 ) ^ 2 + ( #3 ) ^ 2 ) ^ ( 0.5 )
     }
 }

(At the time of writing, this needs the development version of expl3 rather than the CTAN release.)

On the 'bonus' part to the question, I'd simply pass the values using V-type expansion. TeX grouping does not work like Lua grouping, so you can't group in the way you'd like.

share|improve this answer
    
Thanks for the V. And I've missed not being able to use expressions for fp stuff so I'm looking forward to that being in the official version. –  Loop Space May 18 '12 at 5:35

Will Robertson has sent me an eTeX translation of this LaTeX3 solution, which I have adapted substantially. The difference between Will's and Martin Scharrer's solutions is that Martin's scheme deals with only one type of group (semi simple group). See also etextools's \AfterGroup, which works for all groups but doesn't accumulate code as Will's scheme does.

\documentclass{article}
\usepackage{catoptions}
\makeatletter
% The star (*) variant executes code #1 within the group. #1 must be 
% executable in this case. The un-starred version simply takes the 
% material outside the group.
\robust@def*\aftergroupexecute{\cpt@testst\cpt@aftergroupexecute}
\robust@def\cpt@aftergroupexecute#1{%
  \ifboolTF{cpt@st}{#1}{}%
  % \ag@elt is local to current group:
  \def\ag@elt{aftergroup@execute@\romannumeral\currentgrouplevel}%
  \ifcsndefTF\ag@elt{}{%
    \aftercsname\aftergroup\ag@elt
    % for post \aftergroup resetting:
    \csn@xdef\ag@elt{\gundefcsn{\ag@elt}}%
  }%
  \csn@xdef\ag@elt{\expandcsnonce\ag@elt\unexpanded{#1}}%
}    
\makeatother
% Tests
\begin{document}
\def\x{FOO}
\def\y{ZOO}
\begingroup
  \begingroup
    \hbox{%
      \vbox to 5cm{%
        $
          \aftergroupexecute{\def\x{foo}}%
          \aftergroupexecute{\def\y{zoo}}%
        $
        %\show\x
        \show\y
      }%
      \show\x
    }%
    \show\x
  \endgroup
  \show\x
\endgroup
\show\x
\end{document}

EDIT

Will, Please how will your scheme work in the following scenario? This isn't a debate; my aim is to understand your scheme. I want only \includelater and \somemasters outside the adjusted hbox group.

\hbox{%
  \def\classlist{David/Salomon, Michael/Downes, Frank/Mittelbach, David/Carlisle, 
    David/Kastrup, Heiko/Oberdiek, Till/Tantau, Donald/Arseneau, Joseph/Wright, 
    Hans/Hagen, Taco/Hoekwater, Hartmut/Henkel, Robin/Fairbain, Ulrike/Fischer, 
    Ulrich/Diez}
  % Let Will's processing function be \do:
  \def\do#1/#2,{%
    \ifx\do#1\else
      \noexpand\elt{\cpttrimspace{#1}}{\cpttrimspace{#2}}%
      \expandafter\do
    \fi
  }%
  \edef\classlist{\expandafter\do\classlist,\do/,}%
  \aftergroupexecute{\def\includelater{Will Robertson}}
  \expandafter\aftergroupexecute\expandafter{\expandafter\def\expandafter
  \somemasters\expandafter{\classlist}}
}
%\show\includelater
\show\somemasters
share|improve this answer
    
A disadvantage to this syntax IMO is that what is in \aftergroupexecute still needs to be expanded if you're passing out results of a calculation. E.g., foo must still consist of non-local material. –  Will Robertson May 21 '12 at 6:14
    
@WillRobertson: I am afraid I don't understand your comment. Perhaps you could send me an email. In the meantime, in the example given, with the *-variant you have the option of executing \def\x{foo} (or any given <code>) within the local group, or simply passing it on to outside the group. In your approach, you hardwired \def#1{#2}, implying that only the definition of #1 could take place within and outside the group. –  Ahmed Musa May 21 '12 at 7:24
    
Let's say you want to take some input and process it using a scratch variable, then output it in \x. So you're writing something like \begingroup \def\@tempa{#1} \processingfunction \y {«something with \@tempa»} \aftergroupexecute{ \def\x{\y} } \endgroup. How do you get \x defined as \y without doing yet more ugly expansion control? –  Will Robertson May 21 '12 at 7:45
    
To be fair I don't think my syntax is perfect either. I now think it's better to separate local assignment and the declaration of "returning" a variable. So if I re-wrote my answer now I'd prefer a syntax like \group_return_variable:N \x where the local value of \x would remain after the group ended. –  Will Robertson May 22 '12 at 2:11
    
Well, I don't know LaTeX3, but \begingroup\def\foo{foo}\aftergroup\foo\endgroup, of course, won't work. –  Ahmed Musa May 22 '12 at 3:52

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.