我经常使用粗体数学,为了避免\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}