11

I use boldface math a lot, so to avoid typing \mathbf every time, I define macros like the following:

\newcommand{\x}{{\mathbf{x}}}

The issue is that macros such as \a, \c, \o exist already. Instead of overwriting these with \renewcommand, which is clearly suboptimal, I followed this answer and define my bold macros for the math mode only:

\usepackage{xparse}
\DeclareDocumentCommand{\newmathcommand}{mO{0}m}{%
    \expandafter\let\csname old\string#1\endcsname=#1
    \expandafter\newcommand\csname new\string#1\endcsname[#2]{#3}
    \DeclareRobustCommand#1{%
        \ifmmode
        \expandafter\let\expandafter\next\csname new\string#1\endcsname
        \else
        \expandafter\let\expandafter\next\csname old\string#1\endcsname
        \fi
        \next
    }%
}

\newmathcommand{\a}{{\mathbf{a}}} \newmathcommand{\c}{{\mathbf{c}}}

This seems to work for most of the cases, except for \S. When I have \S in text mode, the code doesn't compile (it takes an infinite time). The complete source code is appended below for your convenience.

My question is obviously how to fix this. Thank you very much in advance for your help!

\documentclass{article}

\usepackage{amsmath} \usepackage{amssymb} \usepackage{amsthm}

\usepackage{xparse} \DeclareDocumentCommand{\newmathcommand}{mO{0}m}{% \expandafter\let\csname old\string#1\endcsname=#1 \expandafter\newcommand\csname new\string#1\endcsname[#2]{#3} \DeclareRobustCommand#1{% \ifmmode \expandafter\let\expandafter\next\csname new\string#1\endcsname \else \expandafter\let\expandafter\next\csname old\string#1\endcsname \fi \next }% }

\newmathcommand{\S}{{\mathbf{S}}} \newmathcommand{\o}{{\mathbf{o}}}

\begin{document}

Test S: $\S$, \S. Test o: $\o$, \o.

\end{document}

Update:

Same behavior with:

\ifdefined\S
    \let\svS\S
    \DeclareRobustCommand\S{\ifmmode\mathbf{S}\else\expandafter\svS\fi}
\else
    \newcommand{\S}{{\mathbf{S}}}
\fi

or with

\let\svS\S
\DeclareRobustCommand\S{\ifmmode\mathbf{S}\else\expandafter\svS\fi} 

(the compilation ended up with "TeX capacity exceeded")

Update 2:

Following @Davislor's suggestion in the comments:

\let\oldS\S
\renewcommand\S{\ifmmode\mathbf{S}\else\oldS\fi}

I tried the following macro:

\newcommand{\newmathcommand}[2]{
    \ifdefined#1
        \let#1old#1
        \renewcommand#1{\ifmmode#2\else#1old\fi}
    \else
        \newcommand{#1}{#2}
    \fi
}

\newmathcommand{\S}{{\mathbf{S}}}

but I obtained: "Missing \begin{document}".

Update 3:

I received three excellent answers below from @Werner, @cgnieder, and @egreg (please upvote all of them!). Unfortunately it is not possible to accept more than one of them, so let me accept the first one (@Werner's), which is also the only one that can work on old LaTeX kernels (such as provided by TeXlive 2018, the one I am currently using).

To the future reader: It is not recommended to change the behavior of the default commands (see the experts' opinions below), so you should consider instead defining new commands, such as \bx, \bS, etc.

f10w
  • 791
  • I don’t know if this is acceptable to you, but what I would do is pick slightly-longer names that aren’t already taken, perhaps \bS or \setS. – Davislor Jan 07 '21 at 23:20
  • @Davislor Thanks for your comments. The solution in your first comment produced an error: "Command \S already defined". It is somehow similar to something I had tried (see the update in the question). As for changing the names, this is unfortunately not my preferred solution as I have defined and used the entire alphabet with \a,\b,...,\z,\A,\B,...,\Z in many documents. – f10w Jan 07 '21 at 23:31
  • Excuse me, you’re right. \renewcommand, not \newcommand. Or \DeclareRobustCommand works in either case. – Davislor Jan 07 '21 at 23:41
  • @Davislor Thanks. With \renewcommand it works. Let me try building a similar \newmathcommand (cf. the question) with this solution. Could you please tell me what you meant by "with better formatting" in your first comment? – f10w Jan 07 '21 at 23:49
  • @Davislor Could you please have a look at "Update 2"? – f10w Jan 08 '21 at 00:02
  • Removing my comment that didn’t work. The tree I was barking up when @Werner answered was to declare a fully-expandable version of \oldS and \oldo, and check if old#1 had already been defined. But his approach is clearly better. – Davislor Jan 08 '21 at 01:06
  • I don't think creating macros to simplify typing is a very good idea. This makes the code difficult for a third person and for yourself to read even if you reread your code after a while. The search for possible errors is greatly complicated. It is better to use a good editor that allows easy entry. – gigiair Jan 08 '21 at 09:05
  • Thanks again, @Davislor! – f10w Jan 08 '21 at 19:24
  • @gigiair Thanks for your comment, but I do not see anything wrong with defining macros, which I find to be standard in TeX typesetting. The question is rather how to do that correctly. For example, I should have used, say, \bS and \bc instead of changing the behavior of the existing commands \S and \c. – f10w Jan 08 '21 at 19:28
  • @Khue I don’t want to put words in another user’s mouth, but the way I’d put it is: I define macros to make my documents more readable and maintainable, not to save a few keystrokes. For example, depending on who a paper is for, I might want to represent a vector as , or v⃗. If I write \vectorsym{v}, it’s easy to tell what I intended, and easy to change to someone else’s house style. – Davislor Jan 08 '21 at 20:04
  • @Davislor Thanks for the clarification, but I still think that this is rather a personal taste. The readability, clarity, and maintainability of a TeX document highly depend on how and by whom it is used. I personally prefer to see 2\norm{\x}^2 + 2\norm{\y}^2 = \norm{\x + \y}^2 + \norm{\x - \y}^2 in my code, rather than 2\left\| \vectorsym{x} \right\|^2 + 2\left\| \vectorsym{y} \right\|^2 = \left\| \vectorsym{x} + \vectorsym{y} \right\|^2 + \left\| \vectorsym{x} - \vectorsym{y} \right\|^2 (especially when I have hundreds of such formulas). – f10w Jan 08 '21 at 20:50
  • @Khue Probably, no one except you rereads your code to check for any errors. Or maybe you never do. Personally I use Emacs and to type for example \textbf I just have to type three keys. It's as quick as typing \bS or something similar, and the code is still perfectly readable. I wouldn't save any time doing it, it's up to everyone to see. I said that just to initiate some substantive thinking. – gigiair Jan 08 '21 at 22:02

4 Answers4

10

Werner already told you the reason where your command fails: trying to \let commands to LaTeX-robust commands (I know there is a more detailed explanation around here on tex.sx somewhere but I'm unable to find it…)

An up to date LaTeX provides \NewCommandCopy (as well as \RenewCommandCopy and \DeclareCommandCopy) which you can use instead of \let and which will also give you the expected result with LaTeX-robust commands.

Your MWE with only minor changes:

\documentclass{article}

\DeclareDocumentCommand{\newmathcommand}{mO{0}m}{% \expandafter\NewCommandCopy\csname old\string#1\endcsname{#1}% \expandafter\newcommand\csname new\string#1\endcsname[#2]{#3} \DeclareRobustCommand#1{% \ifmmode \expandafter\let\expandafter\next\csname new\string#1\endcsname \else \expandafter\let\expandafter\next\csname old\string#1\endcsname \fi \next }% }

\newmathcommand{\S}{{\mathbf{S}}} \newmathcommand{\o}{{\mathbf{o}}} \newmathcommand{\c}{{\mathbf{c}}}

\begin{document}

Test S: $\S$, \S. \par Test o: $\o$, \o. \par Test c: $\c$, \c{c}.

\end{document}


I'm not convinced, though, that having two different definitions for a command inside and outside of math is a good idea…

cgnieder
  • 66,645
  • That will come in handy! – Davislor Jan 08 '21 at 16:13
  • Thank you very much! I haven't had a chance to test this on a newer TeX installation (I have TeX Live 2018), but based on the number of upvotes, I have absolutely no doubt that it works like a charm on any recent versions! – f10w Jan 08 '21 at 19:23
8

It's quite interesting to see where the infinite loop is started. First we look at the standard definition of \S

% latex.ltx, line 3801:
\DeclareRobustCommand{\S}{\ifmmode\mathsection\else\textsection\fi}

This means, under the current implementation, that there are actually two definitions involved; in a complicated way that's not necessary to explain, the above code does essentially something like

\def\S{\protect\S•}
\def\S•{\ifmmode\mathsection\else\textsection\fi}

where the bullet denotes a space which is part of the name of the second macro (so it's easy to guess that some complicated set up is necessary, but that's not the point here).

Now you want to redefine \S like

\let\old/S=\S
\def\new/S{\mathbf{S}}
\DeclareRobustCommand\S{\ifmmode\new/S\else\old/S\fi}

where, in order to avoid reading ambiguities, / denotes the backslash in the command name obtained from \string\S via

\expandafter\let\csname old\string\S\endcsname=\S

OK, this is the same as doing

\def\old/S{\protect\S•}
\def\new/S{\mathbf{S}}
\def\S{\protect\S•}
\def\S•{\ifmmode\new/S\else\old/S\fi}

We're starting to see something fishy, aren't we? Let's see what happens if \S is found in math mode: each line is what results from macro expansion and command execution of the preceding line; here \protect is the same as \relax.

\S                              % start
\protect\S•                     % expansion
\S•                             % \protect is \relax and disappears
\ifmmode\new/S\else\old/S\fi    % expansion
\new/S\else\old/S\fi            % we're in math mode
\mathbf{S}\else\old/S\fi        % expansion
\else\old/S\fi                  % \mathbf{S} is executed and disappears
\fi                             % \else gobbles everything up to \fi

The final expansion is empty. Good! We get \mathbf{S}!

Now let's see what happens in text mode:

\S                              % start
\protect\S•                     % expansion
\S•                             % \protect is \relax and disappears
\ifmmode\new/S\else\old/S\fi    % expansion
\old/S\fi                       % we're not in math mode, tokens up to \else are gobbled
\protect\S•\fi                  % expansion
\S•\fi                          % \protect is \relax and disappears
\ifmmode\new/S\else\old/S\fi\fi % expansion

Sorry, infinite loop!

Your \let\old/S=\S only saves the “surface” definition of \S, not the “deep” one, which is the most important.

Why doesn't this happen with \c, for instance? Because it's defined (and made robust) in a completely different way

% latex.ltx, line 3732:
\DeclareTextAccentDefault{\c}{OT1}

and

% ot1enc.def, line 63:
\DeclareTextCommand{\c}{OT1}[1]
   {\leavevmode\setbox\z@\hbox{#1}\ifdim\ht\z@=1ex\accent24 #1%
    \else{\ooalign{\unhbox\z@\crcr\hidewidth\char24\hidewidth}}\fi}

With a newer LaTeX kernel, you can use \NewCommandCopy to overcome the problem. I also suggest a “simpler” reimplementation using expl3 features.

\documentclass{article}
%\usepackage{xparse} % not needed with LaTeX 2020-10-01 or later

\ExplSyntaxOn \NewDocumentCommand{\newmathcommand}{mO{0}m} { \exp_args:Nc \NewCommandCopy {khue_kept_\cs_to_str:N #1} { #1 } \exp_args:Nc \newcommand {khue_new_\cs_to_str:N #1}[#2]{#3} \DeclareDocumentCommand {#1} {} { \mode_if_math:TF { \use:c {khue_new_\cs_to_str:N #1} } { \use:c {khue_kept_\cs_to_str:N #1} } } } \ExplSyntaxOff

\newmathcommand{\S}{{\mathbf{S}}} \newmathcommand{\c}{{\mathbf{c}}} \newmathcommand{\o}{{\mathbf{o}}}

\begin{document}

Test a: $\c$, \c{c}

Test S: $\S$, \S.

Test o: $\o$, \o.

\end{document}

However, this is just for academic interest. Define \bS instead.

The problem is not in defining a command to do different things if called in text or math mode: as you see, the original definition of \S does exactly this! But it does so in order to get comparable output in the two cases. Your new \S does completely different things in text or math mode. Not a really good user interface, in my opinion.

egreg
  • 1,121,712
  • Great explanation! I am impressed. Thank you so much! The proposed solution doesn't seem to work on TeX Live 2018, but it's kept at a nice place in my personal library for future use ^^ – f10w Jan 08 '21 at 19:21
7

The problem here stems from commands that are robust. Robust commands don't expand the way regular commands do, so your "old definition" ends up becoming cyclic and therefore runs in an endless loop.

One can test whether a command is defined as being robust using a technique described in How to use \CheckCommand with robust commands? and condition on storing the "expanded" robust command rather than its original definition.

enter image description here

\documentclass{article}

\usepackage{amsmath} \usepackage{amssymb}

\makeatletter \DeclareDocumentCommand{\newmathcommand}{ m O{0} m }{% % Check whether command is robust or not (https://tex.stackexchange.com/a/63734/5764) \ifcsname\expandafter@gobble\string#1\space\endcsname % Command is robust \expandafter\expandafter\expandafter\let\expandafter\csname old\string#1\expandafter\endcsname\expandafter=\csname\expandafter@gobble\string#1\space\endcsname \else % Command is not robust \expandafter\let\csname old\string#1\endcsname=#1 \fi \expandafter\newcommand\csname new\string#1\endcsname[#2]{#3} \DeclareRobustCommand#1{% \ifmmode \expandafter\let\expandafter\next\csname new\string#1\endcsname \else \expandafter\let\expandafter\next\csname old\string#1\endcsname \fi \next }% } \makeatother

\newmathcommand{\S}{{\mathbf{S}}} \newmathcommand{\o}{{\mathbf{o}}} \newmathcommand{\c}{{\mathbf{c}}}

\begin{document}

Test S: $\S$, \S \par Test o: $\o$, \o \par Test c: $\c$, \c{c}

\end{document}

Werner
  • 603,163
  • Thank you so much for the explanation, and of course for the solution! Among the 3 proposed solutions, yours is the only one that works on my (old) TeX Live 2018! – f10w Jan 08 '21 at 19:18
6

The package mathcommand is meant exactly for that:

\documentclass{article}
\usepackage{mathcommand}

\renewmathcommand{\S}{{\mathbf{S}}}

\begin{document} \S $\S$ \end{document}