为什么在纯 TeX 格式中将 \new... 定义为 \outer?

为什么在纯 TeX 格式中将 \new... 定义为 \outer?

在 中plain.tex,DEKnuth 决定将\outer所有分配寄存器的宏定义为:\newcount\newdimen\newskip\newread\newwrite。这禁止它们进入宏的参数或定义:它们只能出现在最外层。

另一方面,Leslie Lamport\outer在编写 LaTeX 格式时基本放弃了原语。特别是,\outer在他的方法中,分配宏已不复存在。

这两种选择各有什么优缺点?其他格式(ConTeXt、LaTeX3)怎么样?

附带问一下:是否有一些与\outer原始相关的历史民间传说?

答案1

的主要目的\outer是捕获错误,例如在宏定义中粗心地分配寄存器或流,或者忘记\long宏参数的右括号。我们经常看到

\newcommand{\xyz}[1]{...\newlength{\xyzlen}\setlength{\xyzlen}{#1}...}

坏的编程;这在 Plain TeX 中不会发生(当然,使用\newskip或),因为这些宏是。为什么这是糟糕的编程?因为每次调用 时,都会分配一个新寄存器,而旧的寄存器将被遗忘和无法使用。我们可以将 视为允许接收值的变量:因此\newdimen\outer\xyz\xyzlen\xyzlen

\newlength{\xyzlen}
\newcommand{\xyz}[1]{...\setlength{\xyzlen}{#1}...}

可以完成同样的工作,而且效率更高。

还必须记住,原始 TeX 每种类型都有 256 个寄存器,但只有 16 个输入和输出流。因此,保留这些寄存器至关重要,而将\new...宏声明为\outer有助于避免寄存器滥用。无论如何,这都是糟糕的编程,即使现在 e-TeX 每种类型都提供了 32768 个寄存器。

然而,\outer使用起来有一些缺点:\outer宏不能出现在跳过的条件文本的中间,所以

\ifdefined\xyzlen\else\newdimen\xyzlen\fi

\xyzlen一旦定义,在 Plain (pdf)TeX 中无效。

另一个例子:很难在\beginsection或之上定义 Plain TeX 宏\proclaim,比如添加自动编号。

我认为,分配宏应该是外部的。在某些情况下,这可能会造成麻烦。例如,定义新类型的浮点数的包将\newcounter在定义中使用。提供一个“内部”版本\@newcounter供包使用会很容易

\outer\def\newcounter{\@newcounter}
\def\@newcounter#1{<what LaTeX does now>}

例如,Plain TeX 表示\outer\def\+{\tabalign}我们可以\tabalign在涉及简单对齐的定义中使用。这至少可以捕获缺乏经验的用户在定义命令时犯的错误。

然而,总有一句老话(就像我以前学会的那样)

\newcount\thmcnt
\def\theorem{\advance\thmcnt 1
  \csname proclaim\endcsname \number\thmcnt\ }}

这使得 TeX 无法看到禁止的控制序列,\theorem其行为方式与 类似\proclaim。但任何了解 的人都\csname可以称为“经验丰富的用户”,即使不是 TeX 专家。

Bruno 在他的评论中提出了另一种方法:

\edef\innerproclaim{\noexpand\proclaim}
\newcount\thmcnt
\def\theorem{\advance\thmcnt 1 \innerproclaim\number\thmcnt\ }}

\noexpand\proclaim之所以有效,是因为在将结果赋值为 的含义之前,的扩展已经执行\innerproclaim,而\noexpand前面的 使得 TeX 在赋值时看到一个等同于 的控制序列\relax,而 允许出现在替换文本中。同样,\edef\noexpand 行家有资格成为经验丰富的用户。

相关内容