通过 \expandafter 延迟标记序列

通过 \expandafter 延迟标记序列

用例:定义一个循环遍历输入名称列表的命令,并从每个名称定义一个命令。

在下面的工作代码中,请注意\expandafter\definput\expandafter{\x}循环内的\x作为循环变量的。
必要性很明显:我们需要先扩展\x到循环范围内的每个值,然后再扩展\definput每个值。

作为程序员,我想说“\x在扩展循环体的其余部分之前,请先替换所有地方的循环值。”
这样做是非直观的,需要在循环体中最终消耗循环变量的每个其他宏上明确“等待扩展我”。

为了使情况复杂化,延迟扩展的工具是\expandafter,它仅对以下 2 个标记进行操作,因此您需要继续链接\expandafter直到到达循环变量。
从函数式编写的角度来看,解决方案是:(expandafter('\definput', expandafter('{', '\x'))后跟普通标记'}')。
这会扩展\x到每个迭代的值,然后{扩展到自身,然后\definput,消耗{,迭代值,和}

来自其他编程语言,您可以轻松地将事物组合在一起,这种输入链感觉就像拔牙一样。

问题:

  • 有没有办法将它们组合在一起\definput{用一个说“不要扩大这个组” \expandafter
  • 这个问题对于 LaTeX 的标记处理设计来说是否根本?
\documentclass[11pt]{article}
\usepackage{tikz}

% On input x, define \x as "the definition of x"
\newcommand{\definput}[1]{%
  \expandafter\gdef\csname#1\endcsname{the definition of #1}%
}
\newcommand{\defallinputs}[1]{\foreach \x in {#1} {\expandafter\definput\expandafter{\x}}}
% Define \x and \y
\defallinputs{x, y}

\begin{document}
\x\par\y
\end{document}

变成:

the definition of x
the definition of y

作为参考,启发这个问题的用例是:在 \xdef 中使用另一个 \foreach 时,在 \foreach 中使用 \xdef 会出现错误

答案1

使用 expl3 控制扩展更容易,也可以使用不定义需要扩展的循环变量的映射形式

在此处输入图片描述

\documentclass[11pt]{article}

\ExplSyntaxOn
\def\defallinputs#1{
   \clist_map_inline:nn{#1}{\cs_new:cpn{##1}{the~ definition~ of~ ##1}}}
\ExplSyntaxOff

% Define \x and \y
\defallinputs{x, y}

\begin{document}
\x\par\y
\end{document}

答案2

\expandafter预料,不是拖延,是扩张。

循环\foreach是在 PGF 中开发的,目的是执行重复任务,通常是向中添加语句tikzpicture

这样, (或任何您使用的控制序列)就会被立即“使用”,因为语句会立即执行,并且结果(以某种内部格式)会附加到处理\x时要传递的图形指令列表中。\end{tikzpicture}

随着时间的推移,\foreach它开始用于其他任务,但有\x一个相当大的限制。另一个已知的限制是每个循环都在一个组内处理。

一个常见的问题(我相信这也是你正在尝试做的)是定义一些数学字母表中的字母的命令。假设你想一次性定义所有命令

\C \F \H \N \Q \R \Z

其中 each 代表\mathbb{<letter>}。你可以用 来实现\foreach,但这样很笨拙。当然尝试

\foreach \x in {C,F,H,N,Q,R,Z}{%
  \expandafter\gdef\csname\x\endcsname{\mathbb{\x}}%
}

将不起作用,因为它会导致所有命令都有替换文本\mathbb{\x}

对于这种特殊情况你可以这样做

\foreach \x in {C,F,H,N,Q,R,Z}{%
  \expandafter\gdef\csname\x\expandafter\endcsname\expandafter{\expandafter\mathbb\expandafter{\x}}%
}

哎呀!对于你来说,代币的数量\expandafter会高得令人尴尬。

您可能会使用参数反转的技巧,但还有更好的方法。

\documentclass{article}
\usepackage{amssymb}

\ExplSyntaxOn
\NewDocumentCommand{\definelettercommands}{O{}mO{}m}
 {% #1 = optional prefix
  % #2 = list of letters
  % #3 = optional postfix
  % #4 = template
  \fink_dll:nnnn { #1 } { #2 } { #3 } { #4 }
 }

\cs_new_protected:Nn \fink_dll:nnnn
 {
  \clist_map_inline:nn { #2 }
   {
    \cs_set_protected:Nn \__fink_dll_temp: { #4 }
    \cs_new_eq:cN { #1 ##1 #3 } \__fink_dll_temp:
   }
 }

\ExplSyntaxOff

\definelettercommands{C,Q,R}{\mathbb{#1}}

\definelettercommands[c]{A,B,C}{\mathcal{#1}}

\definelettercommands{a,b,c}[frak]{\mathfrak{#1}}

\begin{document}

$\C\Q\R$

$\cA\cB\cC$

$\afrak\bfrak\cfrak$

\end{document}

这里#1的第四个参数将成为循环中的当前项,因为根据规则,##1当传递给时它会变成\clist_map_inline:nn,所以在第一种情况下,你得到的等价于

\clist_map_inline:nn { #1 ##1 #3 } { \mathbb{##1} }

\__fink_del_temp:通过首先定义然后使用的间接方式\cs_new_eq:cN是为了在命令已经定义的情况下获得更好的错误消息和恢复。

消化这种抽象概念需要一些时间,但这是值得的。

在此处输入图片描述

如果没有前缀和后缀的“复杂性”,也不使用间接寻址,那么

\ExplSyntaxOn
\NewDocumentCommand{\definelettercommands}{mm}
 {
  \clist_map_inline:nn { #1 }
   {
    \cs_new_protected:cpn { ##1 } { #2 }
   }
 }
\ExplSyntaxOff

然后调用\definelettercommands{C,Q,R}{\mathbb{#1}}就会变成

\clist_map_inline:nn {C,Q,R}
 {
  \cs_new_protected:nn { #1 } { \mathbb{#1} }
 }

在这里,您可以识别出您的周期的类似物\foreach,但却没有令人讨厌的\x

答案3

来自其他编程语言,您可以轻松地将事物组合在一起,这种输入链感觉就像拔牙一样。

我相信你。;-) 但是 TeX 并非旨在成为一种编程语言。它旨在成为一种排版语言。TeX 的编程范式不同于函数式/过程式/面向对象编程语言的编程范式。在熟悉 TeX 的阶段,不要试图将 Pascal、C、C++、Java 等编程语言的概念/术语转移到 TeX,而是严格遵循 Donald E. Knuth 的 TeXbook 中介绍的概念和术语,这对您自己有好处。

问题:

  • 有没有办法将它们组合在一起\definput{用一个说“不要扩大这个组” \expandafter

不。\definput{是两个标记。只要是关于 TeX 的扩展和宏编程,就把 TeX 想象成一个工厂,把标记想象成在装配线上一个接一个地放置的物品。
装配线经过工厂的几个部门,在那里做不同的事情。
在扩展部门,可扩展标记和构成其参数的后续标记从装配线上移除,取而代之的是,根据宏定义或所讨论的可扩展标记的含义,将标记放到装配线上,形成替换文本。“文本”可能会产生误导,因为替换文本也是由标记组成的。
在扩展部门,一切都是关于装配线上的精美小物品(标记),它们从装配线上移除/被其他精美小物品替换。
如果在扩展部门\expandafter遇到,则将其从装配线上移除,然后 TeX 不会关注装配线上的下一个标记,而是关注装配线上的倒数第二个标记,即,如果倒数第二个标记可扩展,则扩展该标记,以便在装配线上,下一个标记后面要么跟着倒数第二个标记(如果该标记不可扩展),要么跟着替换倒数第二个标记的那些标记(如果倒数第二个标记可扩展)。

似乎“组合在一起”是指一种类似于\expandafter但导致 TeX 聚焦于下一个倒数第二个标记而不是下一个倒数第二个标记的机制。TeX
没有这样的功能。它不是必需的。

正如您在问题中已经注意到的那样,您可以链接\expandafter\expandafter\definput\expandafter{\x}

如果您希望避免使用长\expandafter链,或者要链接的标记序列的长度是不可预测的,例如,它来自可能包含来自任意用户输入的标记的宏参数,则可以在应用短链后交换宏参数\expandafter,例如:

\expandafter\tokenA\expandafter\tokenB\expandafter\tokenC\expandafter\tokenD
\expandafter\tokenE\expandafter\tokenF\expandafter\tokenG\expandafter\tokenH
\expandafter\tokenI\expandafter\tokenJ\expandafter\tokenK\expandafter\tokenL
\expandafter\tokenM\expandafter\tokenN\expandafter\tokenO\expandafter\tokenP
\expandafter\tokenQ\expandafter\tokenR\expandafter\tokenS\expandafter\tokenT
\expandafter\tokenU\expandafter\tokenV\expandafter\tokenW\expandafter\tokenX
\expandafter\tokenY\expandafter\tokenZ\ExpandThisFirst{Argument A}{Argument B}%

结果是相同的

\newcommand\ExchangeArgs[2]{#2#1}%
%
\expandafter\ExchangeArgs
\expandafter{\ExpandThisFirst{Argument A}{Argument B}}%
            {\tokenA\tokenB\tokenC\tokenD\tokenE\tokenF\tokenG\tokenH\tokenI\tokenJ
             \tokenK\tokenL\tokenM\tokenN\tokenO\tokenP\tokenQ\tokenR\tokenS\tokenT
             \tokenU\tokenV\tokenW\tokenX\tokenY\tokenZ}%

如果你对于获取所需的结果标记集所需触发的扩展步骤数量很挑剔 — — 其中第一个只需要触发一个扩展步骤\expandafter,后两个需要触发扩展步骤,第一个触发扩展 -chain \expandafter,第二个触发扩展\ExchangeArgs,你可以这样做:

\newcommand\ExchangeArgs[2]{#2#1}%
\csname @ifdefinable\endcsname\stopromannumeral{\chardef\stopromannumeral=`\^^00}%
%
\romannumeral
\expandafter\ExchangeArgs
\expandafter{\ExpandThisFirst{Argument A}{Argument B}}%
            {\stopromannumeral
             \tokenA\tokenB\tokenC\tokenD\tokenE\tokenF\tokenG\tokenH\tokenI\tokenJ
             \tokenK\tokenL\tokenM\tokenN\tokenO\tokenP\tokenQ\tokenR\tokenS\tokenT
             \tokenU\tokenV\tokenW\tokenX\tokenY\tokenZ}%

这样,只需要在 token 上触发一个扩展步骤,\romannumeral因为\romannumeral它本身会触发扩展,直到找到\stopromannumeral表示 TeX-⟨数字⟩- 数量的值不为正,因此\romannumeral会触发其从代币装配线中移除,而不会提供任何代币作为回报。

你可能感兴趣如何知道附加到 csname 宏时的 expandafter 数量?

  • 这个问题对于 LaTeX 的标记处理设计来说是否根本?

是的。LaTeX 的标记处理设计反过来也是 TeX 底层编程范式的一个方面。

顺便说一句:我可能会尝试这样的事情:

\documentclass[11pt]{article}
\usepackage{tikz}

\newcommand{\defallinputs}[1]{%
  \foreach \x in {#1} {%
    \expandafter\gdef\csname\x\expandafter\endcsname\expandafter{\x}%
  }%
}
% Define \x and \y
\defallinputs{x, y}

\begin{document}
\x\par\y
\end{document}

答案4

一般来说,如果你想在“一些标记之后”扩展,你最终会使用辅助宏来完成这项工作。正如 David 所评论的那样,expl3提供了一个用于扩展控制和定义扩展行为的大型宏库。对于“扩展然后执行操作”的简单情况\x,可以执行类似以下操作

\def\foo#1{\expandafter\fooaux\expandafter{#1}}
\def\fooaux#1{<code>}
\foo\x

相关内容