I'm trying to define my own custom environment, and ran into this problem: When I set a macro inside the environment's start code, that macro loses its value at the end of the environment.

To be more precise, with the following LaTeX code

\def\foo{init}
\newenvironment{test}{
    entering test environment, foo = \foo\\
    \def\foo{bar}
    updated to foo = \foo\\
}{
    exiting test environment, foo = \foo\\
}
\begin{test}
    inside environment: foo = \foo\\
\end{test}
outside environment: foo = \foo\\
\begin{test}
    inside environment: foo = \foo\\
\end{test}
outside environment: foo = \foo\\

I get the following output:

entering test environment, foo = init
updated to foo = bar
inside environment: foo = bar
exiting test environment, foo = bar
outside environment: foo = init
entering test environment, foo = init
updated to foo = bar
inside environment: foo = bar
exiting test environment, foo = bar
outside environment: foo = init

As you see, the value of the macro seems to reset right at the end of the environment's end code. This poses two questions:

  1. What is going on here?
    Is this some kind of local scope with a local macro that shadows the global macro's value? Or is it some other mechanism that I have not yet understood?

  2. How can I assign a value to a macro inside an environment in such a way, that I can actually make use of it afterwards?

  • 2
    \begin{test} implicitly starts a group and \end{test} ends it. That means that all assignment that happen in your environment are local to the environment. If you use \gdef\foo{bar} the assignment is global and will be available outside the environment as well (and indeed globally). – moewe Sep 28 at 12:35
  • @moewe Thanks a lot. \gdef seems to be exactly what I need. Would you mind fleshing that out in an answer so that I may vote on it? – cmaster Sep 28 at 12:46
  • 1
    On my way...... – moewe Sep 28 at 12:46
  • Possible duplicate of the first question I asked here: tex.stackexchange.com/questions/47570/… @egreg answered it there too. – Ethan Bolker Sep 28 at 14:26
  • @EthanBolker I would say: Two different questions with similar answers. Your question is about tokens, mine is about macros. I would definitely leave this question accessible for people like me, who are just baffled by their macros losing their values. I for one failed to find your question when I googled my problem, and I don't think I would have realized that its answers had something to say about my problem as well. – cmaster Sep 28 at 15:06
up vote 6 down vote accepted

When you define an environment with

\newenvironment{envname}{begincode}{endcode}

and use it with

\begin{envname}
body
\end{envname}

LaTeX roughly executes

\begingroup
begincode   % as \envname
body
endcode     % as \endenvname
\endgroup

That means that all assignments that happen within begincode, endcode and bodycode are local to the group implicit in the environment.

In your case the \def\foo{bar} then applies only to the environment - outside of the environment the original value is restored. You could go all-in with \gdef and make a global assignment:

\gdef\foo{bar}

But the effect of a \gdef is truly global and in complicated situations with many groups can be different from a local assignment one scope 'above' the environment.

Some assignments like LaTeX counter operations are always global and don't need additional work to break out of environments, but most other assignments are local and will need something like \gdef to also apply outside of the environment.

Note that you made it easy for me by using \def (which has a global companion \gdef). LaTeX's \newcommand and \renewcommand for example do not have global companions and must be replaced with a \gdef construction.

There are very good reasons why LaTeX opens a group upon entering an environment and closes it at exit.

First reason.

The name of the current environment is saved in the macro \@currenvir; when an \end command occurs, LaTeX can check its argument against the meaning of \@currenvir; if they agree, good, otherwise an error is raised. Then the group can be closed and \@currenvir will regain the meaning it had when \begin was executed.

The alternative would be maintaining a stack, each \begin pushing an item to the stack and each \end popping one. But since grouping is useful for other reasons, there was no point in doing it when LaTeX was born (and computer memory scarce).

Second reason

Several environments are used for special typesetting: consider for instance center, flushleft, enumerate, itemize and so on.

Each of these environments may set a lot of parameters and here we see that maintaining a stack would become painful; just think to enumerate nested in itemize. Not to mention technical difficulties when \par is redefined to do something different from its usual action and maybe this redefinition contains code to redefine \par itself as soon as the modified one is executed.

Grouping ensures that the previous values of the parameters modified by the environment are automatically restored at the environment's end.

By the way, this is a reason why there are no chapter, section and so on environments, which might be attractive at first thought: a very long chapter environment, with many inner environments, could fill the stack memory TeX allocates for restoring values. Again, consider when LaTeX was written, with severe computer memory constraints.

Disadvantages

Look at the code for lrbox to see how the problem of setting a box in an environment, but its value preserved at \end{lrbox} is overcome. Interesting code to study, particularly at \hbox{\begingroup\aftergroup} that may surprise at first reading ;-)

It is not possible to define a macro inside an environment so that its value is propagated at the upper level without doing special tricks like the one mentioned; propagating it to two levels above is doubly tricky and so on.

Solution

Use a global definition: \gdef\foo{bar} will propagate the redefinition to all levels. See \global\renewcommand equivalent of \global\def if you want to use LaTeX-like syntax instead, with the usual protections.

  • Thanks for the in-depth explanation. I really appreciate this :-) – cmaster Sep 28 at 14:02

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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