LaTeX 内核到处都使用这两个。例如,显然定义类似内容的正确方法\ifeq{\macro 1}{\macro 2}{true}{false}
是(注意 Martin 的回复:每个案例都应以 开头\expandafter
)
\def\ifeq#1#2{%
\ifx#1#2\relax
\@firstoftwo
\else
\@secondoftwo
\fi
}
而不是
\def\ifeq#1#2#3#4{%
\ifx#1#2\relax
#3%
\else
#4%
\fi
}
然而,据我所知,它们是等效的:
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
甚至还有一个\@firstofone
! 它的作用是:
\long\def\@firstofone#1{#1}
显然,使用这些宏与简单地声明一个或两个参数没有任何功能差异。我一眼就能看出两个可能的小差异:
\@firstoftwo
%
是一个控制序列,因此会占用后续空格。这意味着如果它结束一行,我们不必在它后面放置一个。当然,我们必须%
在很多其他地方放置一个,而且 TeX 实际上不会将一个存储%
在宏的扩展文本中,所以这确实是次要的。用 定义的宏
\@firstoftwo
比通常定义的宏少接受两个参数。因此,TeX 不必存储我的宏接受这两个参数的信息;它只需引用和\ifeq
已经使用的存储。我想这可以减少一点内存使用量。 这两个都不能解释 的用途,因为它只有在作为宏的最后一个标记放置时才可能有用,即使在那里它也不会真正改变后续输入,所以似乎可以将其消除。是否有某种原因会想要这样“设置”下一个标记?还有一种可能性:\@firstoftwo
\@secondoftwo
\@firstofone
这些是
\long\def
'd 而不是\def
'd。因此,即使使用它们的宏不是 ,它们也可以接受多段参数\long
。我猜这允许您在其他宏不是时创建最后一个或两个参数,但实际上,为什么不在您真正想要的时候\long
使用呢?\long
我还遗漏了其他优点吗?它们对速度的影响很小,还是与扩展或执行的互动很棘手,而我却忽略了?真实的造成这种混淆的原因是什么?
答案1
您遗漏了两个非常重要的\expandafter
s。通常使用的“正确”代码是:
\def\ifeq#1#2{%
\ifx#1#2\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
#3
与使用和的宏的区别#4
在于,在处理接下来两个参数中的第一个或第二个参数之前,if 语句已完全处理完毕。这允许该代码例如使用 向前看\@ifnextchar
。否则\else
或\fi
仍然分别位于#3
或之后#4
,这会破坏此类宏。这种首先关闭 if 语句的技术对于递归宏来说也很重要,因为它不会积累大量级联的 if 语句。
宏定义\long
很简单,以便能够最灵活地使用。
该\@firstofone
宏看似无操作,但实际上删除了参数周围的括号。它通常用于 if 语句中,其中\@gobble
在另一个子句中使用(两者再次使用\expandafter
)。
\def\foo#1#2{%
\ifx#1#2%
\expandafter\@firstofone
\else
\expandafter\@gobble
\fi
}
\foo{\bar}{\baz}{Argument code}
答案2
正确的语法\ifeq
应该是
\def\ifeq#1#2{%
\ifx#1#2\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
马丁很好地解释了选择这种方法而不是“四个论点”方法的原因。
那 呢\@firstofone
?嗯,例如,它可以用来去除参数周围的一对括号。另一个用途是使某事成为无用操作;在 LaTeX 内核中,有
\let\AtBeginDocument\@firstofone
它作为宏的一部分执行\document
;因此,如果添加了带有\AtBeginDocument
调用本身的东西\AtBeginDocument
,则将执行此命令的参数(而不是括号中的参数)。这个例子可能很愚蠢,但在现实世界中可能会发生这种情况:
\AtBeginDocument{\AtBeginDocument{do something}}
如果内核说\let\AtBeginDocument\relax
,结果将是喂令牌
{do something}
代码中的和赋值将在组结束时丢失。如果定义正确,LaTeX 将显示\@firstofone{do something}
扩展为的do something
,而无需括号。
当然没有人会写这样的代码;但是包可能会说
\AtBeginDocument{\macro}
而另一个包可能已经\macro
通过调用重新定义了\AtBeginDocument
。
另一个例子:我们想要一个宏,当以正常形式调用时,它不会对其参数执行任何操作,但如果以 * 形式调用,则以斜体打印它。
\newcommand{\macro}{\@ifstar{\textit}{\@firstofone}}
然后\macro{x}
将打印 x,同时\macro*{x}
将打印X。可以省略\@firstofone
,但这不会删除 的(明显)参数周围的括号\macro
。在这种情况下,习惯上使用\@iden
,它与 相同\@firstofone
。
还有另一种情况\@firstofone
非常有用,请参阅 Bruno Le Floch 的回答“双字母字体样式命令(\bf
,,\it
...)会在 LaTeX 中复活吗?
答案3
由于之前的一些回答已经讨论了 的用法\@firstofone
,我也会讨论。一种用法是,当\@firstofone
展开时,其参数的 catcode 是固定的。因此,
\begingroup
\makeatletter
\catcode`\*=\active
\@firstofone{\endgroup
\newcommand{*}}{*}
*
定义要扩展为“其他”字符的活动字符*
。这里,\makeatletter
是必需的,因为\@firstofone
包含一个@
,并且我们将其放在组内:这样,\endgroup
一旦\@firstofone
扩展,读取时所有的 catcode 都会被还原。
答案4
其他一些观点,我认为其他问题中没有提到。
(*)这个答案中有很多虚假的空间,它们仅仅是为了演示目的;或者假设它们在 expl3 的\ExplSyntaxOn
环境中。
TeX 条件究竟如何工作?
如果您没有仔细阅读(TeX)书,而只是浏览命令描述以了解它们的作用,您可能会认为......
\ifx #1#2
⟨some code⟩
\else
⟨some other code⟩
\fi
⟨some code⟩
... 将执行与或完全相同的操作,⟨some other code⟩
取决于前两个标记是否相等。
但是,如果你仔细想想,这些条件是可扩展的。它们如何实现呢?
一个合理的猜测/“心理模型”是
\ifx
抓取下一个 token,直到看到\fi
- 然后比较前两个 token 是否相等
- 然后它“返回”
⟨some code⟩
或者⟨some other code⟩
被执行。
因此在上面的例子中,它将在 1 步内扩展为 或⟨some code⟩
。⟨some other code⟩
有道理,对吧?
主要问题是,这个模型是错误的。现实是……
- 当 TeX 看到 时
\ifx
,它会取出 2 个下一个标记并进行比较。 - 如果比较为真,它会“记住” “这在一个真正的分支内。当
\else
看到下一个时,吞噬直到下一个\fi
;或者当\fi
看到下一个时,将其移除”。 - 如果比较结果为假,它会一直吞噬直到出现
\else
或fi
,然后(可选,如果\else
出现)记住“这是在错误分支内。当\fi
出现下一个时,将其删除”。
例如,如果条件为真,则经过 1 步扩展后,结果为
⟨some code⟩
\else
⟨some other code⟩
\fi
扩展处理器记住这是真分支。查看\else
代码中剩余的不平衡。
你可以自己尝试一下,执行\iftrue\show\else\fi\a
,就会显示\else
。
(也在 TeXbook 中讨论过,第一段“条件”,第 20 章第 213 页 / TeX by Topic,第 13.7 章条件评估 / TeX 简介,第 16 页(简要)。)
为什么?这违反直觉!
我猜测这种行为有以下几个原因:
- 首先,因为标记没有提前标记,所以您可以
\verb
在某些分支内使用 catcode 更改命令(例如)。(大多数情况下)。 - 您不需要将整个 2 个分支读入内存,它们可以稍后读取。这可能是几十年前的主要原因。
如果代码没有提前查找下一个标记会怎么样?
(替代标题:T E X 原始条件)
仍存在一个潜在问题。
假设用户提供#3
或#4
具有一些 TeX 原始条件,它们可能会不平衡:
\ifx #1#2
\fi
\else
⟨some other code⟩
\fi
您可以在这里看到问题。使用\@firstoftwo
/方法时不会发生这种情况。\@secondoftwo
附注:您不能对这个进行大括号保护,在这种情况下,TeX 条件会忽略大括号。(因此有了著名的“大括号破解”\iffalse { \fi }
和\expandafter { \iffalse } \fi
)
附注:代码可以进一步优化。
如果你执行以下操作
\def \change@secondoftwo@to@firstoftwo \fi \@secondoftwo #1#2 {
\fi #1
}
\ifx #1#2 \change@secondoftwo@to@firstoftwo \fi \@secondoftwo {#3} {#4}
你只需要 3 个 token,而不是通常的 5 个\expandafter \@firstoftwo \else \@secondoftwo \fi
。(方法在https://tex.stackexchange.com/a/517265/250119。)
expl3 代码与宏\__prg_TF_true:w
等执行类似操作。
附注,您需要插入\fi
后面的内容,因为如上所述,扩展处理器会记住“我们在真正的分支中”并且\end occurred when \ifx was incomplete
最终会打印例如“ ”错误消息。
(由于某种原因,它实际上不是错误消息,只是打印到终端的警告。)