仅适用于数学模式的新命令:\S 的问题

仅适用于数学模式的新命令:\S 的问题

我经常使用粗体数学,为了避免\mathbf每次都打字,我定义如下宏:

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

问题在于诸如\a\c、之类的宏\o已经存在。与其用 覆盖这些宏,不如\renewcommand我没有用 覆盖这些宏(这显然不是最佳做法),而是遵循了这个答案并仅为数学模式定义我的粗体宏:

\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}}}

这似乎适用于大多数情况,除了\S。当我处于\S文本模式时,代码无法编译(需要无限时间)。为方便起见,下面附上了完整的源代码。

我的问题显然是如何解决这个问题。非常感谢您的帮助!

\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}

更新:

行为相同:

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

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

(编译结果显示“TeX 容量超出”)

更新 2:

按照评论中@Davislor 的建议:

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

我尝试了以下宏:

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

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

但我得到的是:“缺少 \begin{document}”。

更新 3:

我收到了三个出色的答案以下是来自@Werner、@cgnieder 和 @egreg 的建议(请全部点赞!)。遗憾的是,无法接受多个,因此我接受第一个(@Werner 的建议),它也是唯一一个可以在旧 LaTeX 内核上运行的建议(例如 TeXlive 2018 提供的,即我目前正在使用的内核)。

对于未来的读者:不建议更改默认命令的行为(请参阅下面专家的意见),因此您应该考虑定义新的命令,例如\bx\bS等等。

答案1

这里的问题源于健壮的命令。健壮的命令不会像常规命令那样扩展,因此您的“旧定义”最终会变成循环,从而陷入无限循环。

可以使用中描述的技术来测试命令是否被定义为健壮的如何使用 \CheckCommand 与强大的命令?并存储“扩展”的强健命令而不是其原始定义的条件。

在此处输入图片描述

\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}

答案2

Werner 已经告诉过你命令失败的原因:尝试将\let命令转换为 LaTeX 健壮的命令(我知道在 tex.sx 的某个地方有更详细的解释,但我找不到它……)

最新的 LaTeX 提供了\NewCommandCopy(以及\RenewCommandCopy\DeclareCommandCopy),您可以使用它们代替\let和,它们也将通过 LaTeX 强命令为您提供预期的结果。

您的 MWE 仅有微小的变化:

\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}

不过,我并不认为在数学内部和外部对命令有两个不同的定义是个好主意……

答案3

看看无限循环从哪里开始是相当有趣的。首先我们来看看\S

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

这意味着,在目前的实施下,实际上涉及定义;以一种无需解释的复杂方式,上面的代码本质上做了类似的事情

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

其中项目符号表示空格,它是第二个宏名称的一部分(因此很容易猜出需要一些复杂的设置,但这不是这里的重点)。

现在你想重新\S定义

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

其中,为了避免阅读歧义,表示从via/获得的命令名称中的反斜杠\string\S

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

好的,这和做

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

我们开始觉得有些可疑了,不是吗?让我们看看如果\S在数学模式下发现会发生什么:每一行都是宏扩展和前一行命令执行的结果;这里\protect与相同\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

最终的扩展为空。好!我们成功了\mathbf{S}

现在让我们看看在文本模式下发生了什么:

\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

抱歉,无限循环!

\let\old/S=\S只保留了的“表面”定义\S,而没有保留最重要的“深层”定义。

为什么这种情况不会发生在 上\c呢?因为它的定义(和健壮性)完全不同

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

% 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}

使用较新的 LaTeX 内核,您可以\NewCommandCopy解决这个问题。我还建议使用expl3功能进行“更简单”的重新实现。

\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}

然而,这仅仅是为了学术兴趣。\bS应该定义。

问题不在于定义一个命令,如果在文本或数学模式下调用,它会执行不同的操作:如您所见,的原始定义\S正是这样做的!但它这样做是为了获得可比两种情况下的输出。你的\S完全不同文本或数学模式。在我看来,这不是一个真正好的用户界面。

答案4

包裹数学命令正是为此而生的:

\documentclass{article}
\usepackage{mathcommand}

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

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

相关内容