用例:定义一个循环遍历输入名称列表的命令,并从每个名称定义一个命令。
在下面的工作代码中,请注意\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
答案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