114

Environments are allowed to have arguments, but reference to those arguments can only appear in the opening code of the environment, and not the closing code. What is the reason for this, and are there preferred ways around it?

e.g.

\newenvironment{foo}[1]%
{some code #1} % allowed
{some code #1} % not allowed
8
  • 13
    With the (LaTeX3) xparse package, you have \NewDocumentEnvironment where it's possible to refer to the arguments also in the closing code.
    – egreg
    Apr 30, 2011 at 19:59
  • 1
    Could you say how that works? Does \NewDocumentEnvironment cause the start code to create a unique macro name, known to the end code, storing the arguments? Or does it look ahead? Some kind of call stack, perhaps?
    – Ryan Reich
    Apr 30, 2011 at 20:06
  • 4
    @Ryan: 'Neither of the above'. The approach is to store the arguments in a macro, then use it at the end of the environment. As LaTeX environments form groups, it's quite possible to arrange for this to work nicely. (I know because I wrote the current implementation, although the concept is not mine.)
    – Joseph Wright
    Apr 30, 2011 at 20:36
  • @Joseph: you mean there's a macro like \envargs which gets filled each time an environment is called with arguments? So then TeX itself takes care of the "call stack" by saving and restoring its value when passing through nested environments.
    – Ryan Reich
    Apr 30, 2011 at 20:44
  • 1
    @gigabytes egreg's answer specifically mentions xparse as a good solution.
    – Alan Munn
    Dec 6, 2017 at 14:01

8 Answers 8

77
\newenvironment{foo}[1]%
  {...} 
  {...}

this is internally defined as

\def\foo#1{....}
\def\endfoo{..}

The end part has by definition no argument.

\newenvironment{foo}[1]%
  {\def\fooNoI{#1}some code #1}
  {some code \fooNoI}
1
  • 6
    This is unfortunate (-1 for LaTeX), but thank you for the definitive answer that references the internals. +1 Nov 11, 2016 at 21:11
26

The reason why arguments cannot be used in the “close” part of the environment's definition is simple:

\newenvironment{foo}[1]
  {<start-code>}
  {<finish-code>}

actually does something roughly equivalent to

\newcommand{\foo}[1]{<common start-code><start-code>}
\def\endfoo{<finish-code><common finish-code>}

where the <common> codes are the same for all environments. So using #1 in the <finish-code> will make TeX find #1 in the definition of a parameterless macro, which is illegal.

The simplest way for overriding this default behavior is to employ xparse:

\usepackage{xparse}
\NewDocumentEnvironment{foo}{m}
  {some code #1}
  {some code #1}

will work as expected. The number of arguments must be specified by adding as many m tokens as wanted. So two arguments will require {mm}. This is because other argument types can be specified (optional arguments in several positions). Consult the package documentation for more details.

1
  • 5
    As of LaTeX released on 2020-10-01 or later, loading xparse is no longer necessary.
    – egreg
    Oct 22, 2021 at 21:03
26

A workaround is to store information in a macro that is then accessible at the end of the environment. A key-value option parser may come in handy for this. In addition to graphics, PGF also provides a pretty good key-value system that can be used independently with the pgfkeys package.

\documentclass{minimal}
\usepackage{pgfkeys}

\newif\ifbar
\pgfkeys{/mypkg/.is family, /mypkg,
  default/.style = {bar=true, foo={not set!}}, % Initialize so 'defaults' exist
  foo/.store in = \foo,   % Any value assigned to foo will be stored in \foo
  bar/.is if = bar,       % Declare a boolean, defaults to false
}

\newenvironment{fooenv}[1][]{% Argument is optional as defaults were declared
  \pgfkeys{/mypkg/.cd, default, #1}%  Shift prefix to `mypkg` and parse arguments
}{%
 \textbf{\foo}~
 \ifbar
    Bar was true!
 \else 
    Bar was false!
 \fi
}

\begin{document}

  \begin{fooenv}[bar=false, foo={Hello, world!}]
    texting...
  \end{fooenv}

  \begin{fooenv}
    Blah blah.
  \end{fooenv}

\end{document}

This gives the following output:

texting... Hello, world! Bar was false!

Blah blah. not set! Bar was true!

pgfkeys is capable of much more and can be used with plain TeX, LaTeX or ConTeXt. See the "Key Handlers" section of the pgf manual for more details.

2
  • 1
    This is good advice. In fact, key-value parsers are a far superior way of dealing with arguments to high-level functions than the default, since they have meaningful semantics. For example, who can remember which optional argument to the \newtheorem command in amsthm subordinates the counter to another, and which one ties it to another?
    – Ryan Reich
    Apr 30, 2011 at 20:00
  • mini,al is not suitable for minimal examples. Try article instead.
    – cfr
    Jul 12, 2017 at 3:34
21

The reason the end code can't make references to the arguments passed to the start code is that they are expanded separately. That is, if you have an environment myenv taking one argument and you write

\begin{myenv}{myarg}
  some text...
\end{myenv}

then LaTeX expands \begin{myenv}, passing it the single argument myarg, and pastes the result in front of "some text...". The document then proceeds as it will, with other environments possibly opening and closing (perhaps even other instances of myenv) before \end{myenv} is finally reached. When that happens, it gets expanded, but there is no way of knowing anymore what the argument to the original \begin{myenv} was. Thus, there is no way of passing it to the end code unless you chose to save its value.

It's worth examining why this is confusing compared to \newcommand. Both appear to work in the same way:

\newcommand{\mymacro}[1]{macro code with #1}
\newenvironment{myenv}[1]{start code with #1}{end code}

The difference is that a macro is a single thing, which is reflected in the notation: you write \mymacro with a backslash but myenv without, perhaps signifying that it is a higher-level abstraction. Indeed, \newenvironment creates a pair of macros \myenv and \endmyenv which function as described above.

The setup is designed to create the appearance of "blocking off" the document into chunks contained in various environments, but in fact, the unity of each environment is a bit illusory. LaTeX keeps track of the name of the environment it most recently entered, but at no time (barring clever tricks) does it ever "see" the entire environment at once, either forward (when starting) nor backwards (when ending).

15

My way of dealing with the problem:

\documentclass{article}

\newenvironment{myenv}[1]
{\newcommand{\foot}{#1}{\bfseries Beginning(#1)}: }
{{\bfseries The end (\foot)}}

\begin{document}

\begin{myenv}{Oki doki}
This is my environment, which is quite useless.
\end{myenv}

\end{document}
1
13

Environments are simply a TeX technique, used extensively by Knuth to delimit parameters of a macro by other macros and defining two commands one for the starting macro and one for the ending macro.

To see that they are actually two macros, you can try this:

\minipage{30pt} one\endminipage

You can define your own environments easily (including pseudo-parameters) for the ending macro (they are actually parameters of the first) in a non-traditional manner as follows:

\documentclass{article}
\begin{document}
\long\def\minipage #1\endminipage#2{#1,#2}

\minipage one\endminipage{two}
\end{document}
4
  • It's worth noting that this functions a lot differently than the environments defined by \newenvironment, since it absorbs the entire text of the minipage before processing any of it. This breaks \verbatim, for example, though also makes the minipage reusable and protects (for the particularly paranoid) against weird catcode changes.
    – Ryan Reich
    Apr 30, 2011 at 20:03
  • @Ryan Yes for sure it functions differently, but I just wanted to demonstrate some alternatives. As you correctly pointed out in your post in a LaTeX environment, LaTeX just prepends and appends code to some text that the user types in between.
    – yannisl
    Apr 30, 2011 at 20:11
  • I was not cricticizing, and I like this construction as well. However, this question seems intended as a FAQ, and so I wanted to add a bit of detail.
    – Ryan Reich
    Apr 30, 2011 at 20:16
  • Don't you mean they're a LaTeX technique?
    – cfr
    Jul 12, 2017 at 3:37
7

Another possibility is using the environ package:

\documentclass{article}

\usepackage{environ}
\NewEnviron{foo}[1]{\BODY\ #1}

\begin{document}

\begin{foo}{works!} This\end{foo}

\end{document}
5

While Herbert and Ryan Reich answered the reason as why it is not possible, I'll give my solution to solve the problem which is similar to Mateusz Maciejewsk's answer:

\newcommand{\fooArg}{} % dummy macro
\newenvironment{foo}[1]{%
  \renewcommand{\fooArg}{#1}%
  some code #1 %
}{ %
  some code \fooArg %
}

It might not be usable in all situations, but it might solve the problem if you are in hurry or don't want to use specific package.

2
  • 1
    It seems identical rather than similar...
    – percusse
    Aug 2, 2013 at 22:34
  • I find this more concise than it's inspiration.
    – twip
    May 13, 2015 at 16:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .