[Tex/LaTex] How to properly smuggle (with or even without TikZ)

tex-coretikz-pgf

I am seeking to find something that allows me to "broadcast" macros outside a group. Concrete examples include paths and scopes in tizpictures. Here is an M(N)WE.

\documentclass[tikz,border=3.14mm]{standalone}
\usetikzlibrary{calc}
\makeatletter
\let\smuggleoutone\pgfmath@smuggleone
\makeatother
\begin{document}
\begin{tikzpicture}[globalize/.code n args={2}{\xdef#2{#1}},
localize/.code n args={2}{\pgfmathsetmacro{#2}{#1}\typeout{#2}
%\smuggleoutone#1
}]
\begin{scope}[local bounding box=extra]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
\pgfextra{\xdef\myangle{\n1}};
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (extra.north) {using \verb|\pgfextra|};
%
\begin{scope}[local bounding box=globalize,xshift=3cm]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
[globalize={\n1}{\myangle}];
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (globalize.north) {using \texttt{globalize}};
%
\xdef\myangle{7}
\begin{scope}[local bounding box=localize,xshift=6cm]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
[localize={\n1}{\myangle}];
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (localize.north) {attempt to smuggle};
%
\end{tikzpicture}
\end{document}

enter image description here

The two options on the left do partly what I am seeking to do, namely broadcast the macro \myangle outside the path. However, they do it at the expense of making \myangle global. TikZ has some internal commands that may allow one to avoid this, and to just smuggle the macro outside the path. Specifically, @DavidCarlisle suggested in the chat to use pgfmath@smuggleone. However, my above attempts failed, i.e. if I uncomment

%\smuggleoutone#1

the code produces errors.

QUESTION: Can one smuggle the macro outside the group without making it global?

"BONUS": Of course it would be great if there was an explanation what all the smuggle commands do.

"BONUUUUS": Conceivably these methods may be useful independently of TikZ, so if there is a way not to make them depend on TikZ being loaded, this would be great, but is certainly not a requirement.

Best Answer

You can "smuggle" definitions out of their group with the TeX primitive \aftergroup. I'll first explain what \aftergroup does, then give a possible definition of \smuggleone using \aftergroup and finally apply it to your MWE.

The short answer is that you could define \smuggleone (I've removed "out" from the name) as

\newcounter{smuggle}
\DeclareRobustCommand\smuggleone[1]{%
  \stepcounter{smuggle}%
  \expandafter\global\expandafter\let\csname smuggle@\arabic{smuggle}\endcsname#1%
  \aftergroup\let\aftergroup#1\expandafter\aftergroup\csname smuggle@\arabic{smuggle}\endcsname
}

If you paste in this definition and replace \smuggleoutone#1 by \smuggleone#2 in your MWE it should work. (Note that you were passing the wrong argument to \smuggleoutone, it should have been #2 instead of #1.)


About \aftergroup:

It is possible to insert a single token right after the end of the current group using \aftergroup<token>. You can only smuggle out one token at a time, so if you want to move out something consisting of multiple tokens (like a definition) you'll need to \aftergroup each of these tokens separately. This includes things like braces ({}), so for instance

{\aftergroup\def\aftergroup\abc\aftergroup{\aftergroup A\aftergroup B\aftergroup C\aftergroup}}

is equivalent to {}\def\abc{ABC}.

This is quite a hassle, so the following may be more practical:

{\gdef\somethingunique{\def\abc{ABC}}\aftergroup\somethingunique}

This works by globally assigning \def\abc{ABC} to \somethingunique and inserting that after the end of the group. If ABC is replaced by some macro, say \ABC, that is only defined within the current group and that you want to be fully expanded then you'll want to use \xdef instead:

{%
  \newcommand*\ABC{ABC}%
  \xdef\somethingunique{\def\noexpand\abc{\ABC}}%
  \aftergroup\somethingunique
}

I've inserted \noexpand in front of \abc because we don't want \abc to be expanded. If you only want \ABC to be expanded once you can instead use the slightly more complicated

{
  \newcommand*\ABC{\somethingthatshouldntbeexpanded}%
  \xdef\somethingunique{\def\noexpand\abc{\unexpanded\expandafter{\ABC}}}%
  \aftergroup\somethingunique
}

(The primitives \noexpand, \unexpanded and \expandafter are all explained in this this answer.)

To smuggle the definition of \abc out of a group you can do what I just did above with \ABC replaced by \abc itself. That way \abc will be defined as itself (expanded once) immediately after the end of the group.

There's also \AfterGroup from the etextools package. It acts mostly like \aftergroup, but it takes an argument that can consist of any number of tokens. So, for instance, \Aftergroup{\def\abc{ABC}} inserts \def\abc{ABC} after the current group without all of the aforementioned hassle. There's also a starred version, \Aftergroup*, that does the same thing but first expands its arguments fully.

Don't use the etextools package though! It is apparently buggy and no longer maintained and it is incompatible with a bunch of other packages. (Thanks to Ulrike Fischer for pointing that out, here are a few examples: 1, 2, 3, 4.)

Even though you shouldn't use the package, \AfterGroup itself can be quite useful. It is defined as follows:

\makeatletter %% <- make @ usable in command names
\newcount\ettl@fter
\newrobustcmd\AfterGroup{\@ifstar{\ettl@AfterGroup\@firstofone}{\ettl@AfterGroup\unexpanded}}
\newrobustcmd\ettl@AfterGroup[2]{%
   \csxdef{ettl@fterGroup\number\numexpr\the\ettl@fter+1}%
      {\global\csundef{ettl@fterGroup\number\numexpr\the\ettl@fter+1}#1{#2}}%
   \global\advance\ettl@fter\@ne
   \expandafter\aftergroup\csname ettl@fterGroup\the\ettl@fter\endcsname}
\makeatother  %% <- revert @

Defining \smuggleone:

To smuggle a macro that was already defined past the end of a group, it may be more effective to use \let instead of \def. One advantage is that it will also works for macros with arguments:

{
  \newcommand*\abc[1]{``#1''}%
  \global\let\somethingunique\abc
  \aftergroup\let\aftergroup\abc\aftergroup\somethingunique
}
\abc{This works!}

This leads us to a possible definition of \smuggleone.

\documentclass{article}

\newcounter{smuggle}
\DeclareRobustCommand\smuggleone[1]{%
  \stepcounter{smuggle}%
  \expandafter\global\expandafter\let\csname smuggle@\arabic{smuggle}\endcsname#1%
  \aftergroup\let\aftergroup#1\expandafter\aftergroup\csname smuggle@\arabic{smuggle}\endcsname
}

\begin{document}

\newcommand*\abc[1]{\textbf{#1}}%
{%
  {%
    \renewcommand*\abc[1]{``#1''}%
    \smuggleone\abc
    \abc{Local definition}
  }\par
  \abc{Local definition}
}\par
\abc{Global definition}

\end{document}

output

The reason for the use of a counter here is that if you use \somethingunique every time you're smuggling something, it won't really be unique. Whenever multiple smuggling operations are happening consescutively, because you're using \smuggleone multiple times from within the same group or from a group contained in another one where \smuggleone is used, this will cause trouble. The above command therefore creates \smuggle@<n> the <n>-th time it is used.

This can be made more efficient (memory-wise) by reusing these command sequences as much as possible, as in jfbu's answer.


All of this applied to your MWE:

Here is your MWE with two changes: (1) I've added the definition of \smuggleone and (2) I've replaced %\smuggleoutone#1 by \smuggleone#2.

\documentclass[tikz,border=3.14mm]{standalone}
\usetikzlibrary{calc}

\newcounter{smuggle}
\DeclareRobustCommand\smuggleone[1]{%
  \stepcounter{smuggle}%
  \expandafter\global\expandafter\let\csname smuggle@\arabic{smuggle}\endcsname#1%
  \aftergroup\let\aftergroup#1\expandafter\aftergroup\csname smuggle@\arabic{smuggle}\endcsname
}

\begin{document}
\begin{tikzpicture}[globalize/.code n args={2}{\xdef#2{#1}},
localize/.code n args={2}{\pgfmathsetmacro{#2}{#1}\typeout{#2}
\smuggleone#2
}]
\begin{scope}[local bounding box=extra]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
\pgfextra{\xdef\myangle{\n1}};
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (extra.north) {using \verb|\pgfextra|};
%
\begin{scope}[local bounding box=globalize,xshift=3cm]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
[globalize={\n1}{\myangle}];
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (globalize.north) {using \texttt{globalize}};
%
\xdef\myangle{7}
\begin{scope}[local bounding box=localize,xshift=6cm]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
[localize={\n1}{\myangle}];
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (localize.north) {attempt to smuggle};
%
\end{tikzpicture}
\end{document}
\node[anchor=south] at (globalize.north) {using \texttt{globalize}};
%
\xdef\myangle{7}
\begin{scope}[local bounding box=localize,xshift=6cm]
\path let \p1=($(2,1)-(0,0)$),\n1={atan2(\y1,\x1)} in 
[localize={\n1}{\myangle}];
\node at (1,0) {\myangle};
\end{scope}
\node[anchor=south] at (localize.north) {attempt to smuggle};
%
\end{tikzpicture}
\end{document}

output



Addendum

Here's a \smuggle macro that works up to depth 10. It doesn't let you smuggle anything across eleven borders because 10 is two tokens (yeah, that's a stupid reason). I could make it work for any depth, but I like how short the definition currently is and it seems unlikely that any sane person would need this.

The syntax is \smuggle[<depth>]{<macro>}, and the default <depth> is 1. It works by calling \smuggleone and then also \aftergrouping \smuggle[<depth-1>]{<macro>}.

\documentclass{article}

\newcounter{smuggle}
\DeclareRobustCommand\smuggleone[1]{%
  \stepcounter{smuggle}%
  \expandafter\global\expandafter\let\csname smuggle@\arabic{smuggle}\endcsname#1%
  \aftergroup\let\aftergroup#1\expandafter\aftergroup\csname smuggle@\arabic{smuggle}\endcsname
}
\DeclareRobustCommand\smuggle[2][1]{%
  \smuggleone{#2}%
  \ifnum#1>1
    \aftergroup\smuggle\aftergroup[\expandafter\aftergroup\the\numexpr#1-1\aftergroup]\aftergroup#2%
  \fi
}

\begin{document}

\newcommand*\abc[1]{\textbf{#1}}
{%
  {%
    {%
      \renewcommand*\abc[1]{``#1''}%
      \smuggle[2]{\abc}%
      Definition at depth 3: \abc{Local definition}
    }\par
    Definition of depth 2: \abc{Local definition}
  }\par
  Definition of depth 1: \abc{Local definition}
}\par
Definition at depth 0: \abc{Global definition}

\end{document}

output

Related Question