递归更新宏

递归更新宏

我想\temp使用以下代码在宏中存储并递归更新数学表达式:

\documentclass{article}
\begin{document}
\def\temp{}
\def\update#1_#2{
    \xdef\temptemp{\temp}
    \gdef\temp{\temptemp\frac{#1}{#2}}}
    
\update{a}_{b}
\[\temp\]

\update{c}_{d}
\[\temp\]
\end{document}

该代码运行良好,给出 a/b 和 a/bc/d。

但是,一旦我将其更改\frac{#1}{#2}\underbrace{#1}_{#2},它就会返回错误“!的定义中的参数数量非法\temptemp。”

这是什么问题?我该如何解决?提前致谢。

答案1

您的代码似乎开始工作。让我们添加一些跟踪,以便更好地查看发生了什么:

\documentclass{article}

\begin{document}

\def\temp{}
\def\update#1_#2{
    \xdef\temptemp{\temp}
    \gdef\temp{\temptemp\frac{#1}{#2}}}
    
\update{a}_{b}
\texttt{\meaning\temp}
\[\temp\]

\update{c}_{d}
\texttt{\meaning\temp}
\[\temp\]

\update{e}_{f}
\texttt{\meaning\temp}
\[\temp\]

\texttt{\meaning\temptemp}

\end{document}

在此处输入图片描述

如您所见,您并没有\temp使用新部分进行更新;您\temptemp实际上正在更新。图片中的最后一部分显示您并没有真正获得\frac{a}{b},但是它如何\xdef处理它,这有点难以预测,因为绝大多数排版命令不应该在\edef或中使用\xdef。而 的情况更糟\underbrace

你真的不需要任何特殊的东西:LaTeX 内核有\g@addto@macro

\documentclass{article}

\newcommand{\container}{}
\newcommand\update{}
\makeatletter
\def\update#1_#2{%
  \g@addto@macro\container{\underbrace{#1}_{#2}}%
}
\makeatother

\setlength{\parindent}{0pt}% just for this example

\begin{document}

\update{a}_{b}
\texttt{\meaning\container}
\[\container\]

\update{c}_{d}
\texttt{\meaning\container}
\[\container\]

\update{e}_{f}
\texttt{\meaning\container}
\[\container\]

\end{document}

在此处输入图片描述

答案2

您正在\xdef对由 声明的前一个宏进行完全扩展(),\update但是\underbrace它是一个更复杂的宏,它在前言中使用了\halignwith ,如果不立即处理,就无法完全扩展。#

您可以更新不带扩展的数学公式列表:

\def\update#1_#2{\addto\temp{\underbrace{#1}_{#2}}}

其中\addto定义为:

\long\def\addto#1#2{\expandafter\def\expandafter#1\expandafter{#1#2}}

答案3

underbrace是受 LaTeX\protect机制保护的强大命令。要使该机制正常工作,您需要以下protected@函数。

\documentclass{article}
\begin{document}
\def\temp{}

\makeatletter

\def\update#1_#2{%
    \protected@xdef\temptemp{\temp}%
    \gdef\temp{\temptemp\underbrace{#1}_{#2}}}

    
\update{a}_{b}
\[\temp\]

\update{c}_{d}
\[\temp\]
\end{document}

TeX 中的扩展控制很复杂。此外,在这种情况下,您根本不需要 x 扩展。有专门的宏用于此目的。

\documentclass{article}
\usepackage{etoolbox}
\begin{document}
\def\temp{}

\makeatletter

\def\update#1_#2{%
    \appto\temp{\underbrace{#1}_{#2}}}

    
\update{a}_{b}
\[\temp\]

\update{c}_{d}
\[\temp\]
\end{document}

或者参见\tl_put_right:Nnexpl3,但此处下划线的 catcode 存在一些“复杂情况”。

答案4

\temp许多 LaTeX 软件包都有一个临时宏。因此,不是源自您的代码可能会覆盖\temp源自您编写的代码的(重新)定义。

为了防止此类冲突,下文中\Mytemp使用 代替\temp

Donald E. Knuth 在他的 TeXbook 中将 TeX 比喻为有眼睛、嘴巴和消化道的野兽。

我尝试用这个类比来概述 .tex 文件处理的不同阶段,以回答

\def使用/\gdef时,当组成定义文本的标记进入 TeX 的食道时,扩展会被抑制。因此,使用/\def\gdef,组成定义文本的标记在到达执行分配的胃部时不会扩展。

当构成定义文本的标记进入 TeX 的食道时,带/\edef的扩展不会被抑制。因此,带/ 的构成定义文本的标记在“反流过程”中到达执行分配的胃部时会“完全”扩展。\xdef\edef\xdef

关键点在于:\underbrace本身是一个宏,在某个扩展阶段会产生一些#。 当扩展完成并在 TeX 的胃中尝试执行底层赋值时\xdef,这些#会被错误地用作要定义/重新定义的宏的某些参数的引用。

除此之外,您可能还想应对这样一个事实,即定义可以嵌套在定义内部,从而每个“嵌套级别”都需要另一级别的双倍哈希来表示该定义级别中的定义参数,并且扩展宏会将该宏的定义文本中出现的两个连续哈希减少为一个哈希。

例如,如果你这样做

\long\def\hashcarrier{################A}
\long\xdef\hashcarrier{\hashcarrier B}
\show\hashcarrier
\long\xdef\hashcarrier{\hashcarrier C}
\show\hashcarrier
\long\xdef\hashcarrier{\hashcarrier D}
\show\hashcarrier
%% If you activate the following lines you get error-message about 
%% illegal parameter number in definition of \hashcarrier because 
%% things were reduced to single hashes that are taken for reference 
%% to parameters of the current definition-level while the parameter-text
%% of the current definition-level doesn't denote any parameter at all
%% and parameters must be numbered consecutively from 1 to 9 instead of
%% a thing like A:
%\long\xdef\hashcarrier{\hashcarrier E}
%\show\hashcarrier
\csname bye\endcsname
\stop

您会在终端/.log 文件中得到以下输出

> \hashcarrier=\long macro:
->########AB.
l.3 \show\hashcarrier
                     
? 
> \hashcarrier=\long macro:
->####ABC.
l.5 \show\hashcarrier
                     
? 
> \hashcarrier=\long macro:
->##ABCD.
l.7 \show\hashcarrier
                     
? 

\long意味着如果所讨论的宏处理参数,则每个参数可能包含控制字标记\par。)

\xdef您可以通过和来防止不必要的扩展并减少哈希数量\unexpanded

\documentclass{article}

\newcommand\Mytemp{}%
\long\def\updateMytemp#1_#2{%
  \long\xdef\Mytemp{\unexpanded\expandafter{\Mytemp\underbrace{#1}_{#2}}}%
}%

\begin{document}

\updateMytemp{a}_{b}
\show\Mytemp
\[\Mytemp\]

\updateMytemp{c}_{d}
\show\Mytemp
\[\Mytemp\]
\end{document}

或者 -- 传统风格 -- 通过标记寄存器的扩展\xdef\the

\documentclass{article}

\newtoks\MyScratchtoks
\newcommand\Mytemp{}%
\long\def\updateMytemp#1_#2{%
  \MyScratchtoks\expandafter{\Mytemp\underbrace{#1}_{#2}}%
  \long\xdef\Mytemp{\the\MyScratchtoks}%
}%

\begin{document}

\updateMytemp{a}_{b}
\show\Mytemp
\[\Mytemp\]

\updateMytemp{c}_{d}
\show\Mytemp
\[\Mytemp\]

\end{document}

我建议使用 LaTeX 做一些更通用的事情\g@addto@macro

\documentclass{article}

\newcommand\Mytemp{}%
\makeatletter\newcommand\updateMytemp[1]{\g@addto@macro{\Mytemp}{#1}}\makeatother

\begin{document}

\updateMytemp{\underbrace{a}_{b}}
\show\Mytemp
\[\Mytemp\]

\updateMytemp{\underbrace{c}_{d}}
\show\Mytemp
\[\Mytemp\]
\end{document}

通过所有三个示例,您会在控制台和 .log 文件中看到类似以下消息:

> \Mytemp=macro:
->\underbrace {a}_{b}.
l.9 \show\Mytemp
              
? 
> \Mytemp=macro:
->\underbrace {a}_{b}\underbrace {c}_{d}.
l.13 \show\Mytemp

通过所有三个示例,您将获得以下 pdf 输出:

在此处输入图片描述

另一个提示:

如果要定义的命令已定义或由于名称中包含前导短语“end”而无法定义,则LaTeX 的\newcommand等会触发错误消息。通过将通过/ / /执行的分配包装到 LaTeX 的-test 中,您可以实现相同的行为。\NewDocumentCommand\def\gdef\edef\xdef\@ifdefinable

例如,不仅仅是

\long\def\updateMytemp#1_#2{%
  \long\xdef\Mytemp{\unexpanded\expandafter{\Mytemp\underbrace{#1}_{#2}}}%
}%

你会做

\makeatletter
\@ifdefinable{\updateMytemp}{%
  \long\def\updateMytemp#1_#2{%
    \long\xdef\Mytemp{\unexpanded\expandafter{\Mytemp\underbrace{#1}_{#2}}}%
  }%
}%
\makeatother

如果你想知道\makeatletter/\makeatother是关于什么的:

简化版的 TeX/LaTeX 通常只允许普通字母a..z/A..Z作为控制字标记名称的组成部分。

\makeatletter是一个指令,用于告诉 LaTeX,从今以后@也应允许将其作为控制字标记名称的组成部分。

\makeatother 是一个指令,用于告诉 LaTeX,从今以后@将不允许将 a 作为控制字标记名称的组成部分。

如果没有\makeatletter/,\makeatother您可以使用它\csname..\endcsname来从介于两者之间的内容中收集控制序列标记的名称\csname..\endcsname 并传递该控制序列标记:

\csname @ifdefinable\endcsname{\updateMytemp}{%
  \long\def\updateMytemp#1_#2{%
    \long\xdef\Mytemp{\unexpanded\expandafter{\Mytemp\underbrace{#1}_{#2}}}%
  }%
}%

相关内容