4

As is noted in other questions, such as Global scope or permanent length or savebox, the \setlength command does not always have global effect. In many cases, that is desirable. But when the effect must be global, using \global\setlength often does not work.

I tend to think globally, so to speak. In most cases, when I set a length, I expect it to be nailed down. So I often have to write expressions like this: \setlength\something{value}\global\something=\something. That works well.

So I ask myself, why not define a macro \gsetlength that does it all? MWE:

% !TeX TS-program = LuaLaTeX
% !TeX encoding = UTF-8
% Using LuaLaTeX because I use it in real documents. Probably the same in pdflatex.
\documentclass{article}
\usepackage{calc} % Because I use it in real documents.
\gdef\gsetlength#1#2{\setlength#1{#2}\global#1=#1} % Should this always be OK ?
%
\newlength\mylengthone
\setlength\mylengthone{1pt}
\newlength\mylengthtwo
\setlength\mylengthtwo{2pt}
%
\newlength\mylengththree
\gsetlength\mylengththree{3pt}
\newlength\mylengthfour
\gsetlength\mylengthfour{4pt}
%
\newlength\mytesta % one level of global
\gsetlength\mytesta{\mylengthone+\mylengthtwo}
\newlength\mytestb % two levels of global
\gsetlength\mytestb{\mylengththree+\mylengthfour}
%
\newlength\mytestc
{\setlength\mytestc{\mylengthone+\mylengthtwo}} % Grouped, not global
\newlength\mytestd
{\gsetlength\mytestd{\mylengthone+\mylengthtwo}} % Grouped, global.
%
\begin{document}
\the\mytesta\par % Expecting 3.0pt.
\the\mytestb\par % Expecting 7.0pt.
\the\mytestc\par % Expecting 0.0pt because \setlength was within group.
\the\mytestd\par % Expecting 3.0pt because \gsetlength.
\end{document}

The above code works as expected. Now for my question: Is it something that I can always expect to work, especially since I use the calc package? Or am I in danger of bumping up against hidden expansion or catcode problems?

Using \gsetlength makes my code shorter, and more readable. But I am nervous...

EDIT: Provided longer MWE per request by DC.

EDIT2: For most users, the information provided by David Carlisle will be what you need. That is, inspect your code to ensure that you are not setting the length in such a way that it is locally limited to a group, which might be delimited by extraneous braces.

But in my own case, the answer provided by Heiko was accepted. That's because I have a very large document class, with numerous nested conditionals and values that are manipulated and passed from place to place. The number of them is not so large as to use of all TeX resources, because once my main document text begins, it does not use a lot of code that would choke TeX. In particular, I am not using TiKz, bibliography, or anything like that.

LATE EDIT: I just discovered the \deflength command, in the etoolbox package. According to its description, it supports \global when requested (unlike \setlength).

  • why would you need this (often) certainly it's not needed in any of the settings in this example which all have global scope anyway. (If you do want to do this you would be better to make the original assignment to an always local scratch register than globally assign to your always global #1 otherwise you can kill the save stack. – David Carlisle Dec 13 '17 at 16:59
  • do you have an example where you need to set a length with global scope from inside a group? – David Carlisle Dec 13 '17 at 17:04
  • 2
    Whenever I see something like \global\let#1=#1 my eyes weep. – egreg Dec 13 '17 at 17:08
  • 2
    @RobtAll finally a good justification for this code ^^^^ – David Carlisle Dec 13 '17 at 17:09
  • 1
    @RobtAll the sledgehammer it is then:-) – David Carlisle Dec 13 '17 at 19:25
4
  • The mixed assignments, local and global, are already addressed by David's answer.

  • The ending of the macro definition with #1 in \global#1=#1 is quite dangerous. Consider #1 as \dimen1 (odd register numbers < 10 are for global assigments) and the next tokens are '012345foo'. Then, TeX tries to assign \global\dimen1=\dimen1012345. This is fixed by ending the assignment by \relax.

Suggestion for an improved version:

\makeatletter
\gdef\gsetlength#1#2{%
  \begingroup
    \setlength\skip@{#2}% Local assignment to a scratch register.
    \global#1=\skip@    % Global assignement to #1;
                        % \relax is not necessary because of the following \endgroup.
  \endgroup             % \skip@ is restored by end of group.
}
\makeatother

The local assignment of \setlength is done to the local scratch register \skip@ inside a group. Then the global assignment to #1 is done. After the group end, the locally changed value of \skip@ is restored, but the globally assigned \skip@ keeps the value.

  • Excellent point. Seems that \relax is needed in my original code. The improved code is a bit arcane, but does the job. – user139954 Dec 13 '17 at 17:57
  • 1
    @RobtAll. The updated answer uses \skip@ instead of \dimen@, because \setlength covers both. An explanation is added to make it less "arcane". – Heiko Oberdiek Dec 13 '17 at 19:40
4

The save stack on modern systems is a lot larger than it used to be but still the \global#1=#1 idiom uses it up and will cause fatal error if done often enough

\documentclass{article}

\newlength\zzz
\newlength\Lzzz

\newcount\ccc

\begin{document}

{

\loop
\Lzzz=5pt \global\zzz=\Lzzz
\Lzzz=6pt \global\zzz=\Lzzz
\ifnum\ccc<20000
\advance\ccc 1
\repeat

}
\end{document}

runs without error and the log shows

 23i,1n,17p,109b,36s stack positions out of 5000i,500n,10000p,200000b,80000s

so at most 36 stack positions were used up (which is in latex core code, not the code visible in the document)

However if you try the version using a single length and making local and global settings to the same register:

\documentclass{article}

\newlength\zzz

\newcount\ccc

\begin{document}

{

\loop
\zzz=5pt \global\zzz=\zzz
\zzz=6pt \global\zzz=\zzz
\ifnum\ccc<20000
\advance\ccc 1
\repeat

}
\end{document}

Then you get no output just:

! TeX capacity exceeded, sorry [save size=80000].
<to be read again> 
                   \global 
l.16 \repeat

!  ==> Fatal error occurred, no output PDF file produced!

and all 80000 save stack positions have been used up saving values of the register.

This is why registers are always separated between local and global use, for example the scratch register with odd numbers, 1,3,5,7,9 are always used globally and those with even numbers, 0,2,4,6,8 are always used locally.

  • Ah, I hadn't thought of that. Will investigate further, and get back later. – user139954 Dec 13 '17 at 17:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy