(为什么)我们不能获得每个命令的“完全可扩展”版本吗?

(为什么)我们不能获得每个命令的“完全可扩展”版本吗?

我刚刚开始弄清楚 LaTeX 中的“可扩展”是什么意思,以及为什么拥有尽可能扩展的命令很有用。特别是:

  • 如果您想要将\typeout一些字符串添加到日志文件,则需要能够一直扩展到它。
  • 如果你想做任何类型的单元测试(比如qstest),您至少需要能够扩展到您想要比较的字符串。

我已经阅读了很多关于这个主题的内容。据我所知,如果命令以某种方式改变“状态”,即使用\def\let等,则该命令不可扩展。特别是,我还阅读了这个问题“为什么不是所有东西都可以扩展”。但我的问题没有那么理论化。我想知道为什么我还没有遇到任何“解决方案”。

事实上,“让一切可扩展”在概念上非常简单。TeX 完全有能力排版'不可扩展'命令的结果,只需在执行处理器中执行状态更改即可。重点是,TeX 最终会得到最终的字符串。

(编辑:请注意,我在这里谈论的是文本字符。我理解人们无法捕捉诸如图像、灵活的空格或字符串中的状态操作之类的概念。)

我觉得我们无法以某种方式拦截并利用它,这似乎令人难以置信。我本以为有人会创建这样的命令:

\getresult { <expr> } { <result-macro> }

它不可扩展,但我们仍然可以获得完全扩展的结果以便记录它...比较它...你有什么。

类似这样的问题这个告诉我这还没有完成。

为什么?

好吧……我愿意作弊。如果我们生成一个额外的 TeX 进程来完全评估表达式并(几乎)排版它,会怎么样?我们能用这种方式得到字符串吗?不\scantokens使用类似的技巧来克服另一个固有的 TeX 限制(一旦读取标记,catcode 就会固定)?

能做到吗?

答案1

问题在于这个假设基本上是错误的:

重点是,TeX 最终会得到最终的字符串

它与 C 及其宏预处理器 cpp 不同,C 和它的宏预处理器会先进行所有扩展,从而生成一个 C 文件的扩展版本,该文件可以传递给 C 编译器(或因调试或其他原因而被拦截)。在 TeX 中,非扩展操作和扩展是密不可分的。

经典的例子是

    \setbox0\hbox{hello}\the\wd0

\wd0是可扩展的,并且扩展到框 0 的宽度的十进制表达。但是,直到完成将文本排版到框中的非可扩展操作后,才能将其扩展。

所以鉴于

 \def\foo#1{\setbox0\hbox{#1}\the\wd0}

您不能拥有该命令的可扩展版本\foo


可扩展和不可扩展之间的区别通常是任意的。所以答案是,在 TeX 中无法做到这一点。这就像说我的名字是 David。存在不同的历史,其中不同的事情可能是真实的,但我们有我们拥有的系统。

考虑算术:在经典 TeX 中,要打印比某个值多一个的值,你必须

\advance\mycount by 1 \the\mycount

这是一个不可扩展的操作。在 etex 中,你可以

\the\numexpr\mycount+1\relax

这是一个可扩展的操作。没有原因人们可以争论为什么一个版本的算术是可扩展的,而另一个版本不是。它就是可扩展的。如果有人扩展 TeX(eTeX、pdftex xetex、tex 是 luatex 的一部分),那么任何添加的新原语都必须归类为可扩展或不可扩展,因为 TeX 编程语言要求这种区别。命令是可扩展的或不可扩展的,因为设计它们的人是这样分类的。

正如评论中所述,将片段写出到运行不同 TeX 实例的子进程的可能解决方法无济于事,即使\write可扩展也是如此。评估任何参数的结果都高度依赖于上下文(它可能涉及引用、文档内的定义或引用作业启动的时间),通常不可能在子进程中生成与原始进程中生成的相同的文本。


可能值得注意的是,问题“完全可扩展”中引号中的术语定义不明确(或无法定义),这是围绕扩展主题产生混淆的原因之一。正如之前的问题中讨论的那样 完全可扩展宏的优点和缺点 更好的术语是“在仅扩展上下文中是安全的”。在仅扩展上下文中是安全的命令包括\edef字符\write标记和\relax注释中提到的原语。

答案2

理解 TeX 的“可扩展”一词有些棘手,因为 TeX 命令序列实际上有两种结果(例如\foo,通常称为“宏”,但我不会这样称呼它,因为它带有宏扩展的包袱,我试图揭开它的神秘面纱,而不是进一步混淆)。我将使用虚构的术语来描述这些。

  • 第一种结果是输入,这就是“扩展”之后发生的情况\foo。这只是\foo用其他可以被 TeX 进一步扫描的标记替换。

  • 第二种结果是输出,当结果为不再由 TeX 扫描。它有两种子类型:
    (a)内部输出。这就是编程构造所喜欢\def\let做的事情,以及其他类似的任务,\count0=1这些任务并不完全由控制序列实现。
    (b)外部输出. 这就是排版。但是,即使什么都没有进入页面,它仍然被放置在内存中的一个框中,并且框的内容使用字体和明确的间距进行呈现。

TeX 的操作就像是在一个流中进行的长爬行输入代币可以转换为外部输出(如果它们是可直接排版的材料,如字母或其他字体符号,或者是方框等),内部输出,如果它们是某种类型的作业,因此“保存”它们的结果以供以后使用,或更多输入,如果它们只是直接扩展回输入流。当然,这些过程可以混合使用,例如当您将作为外部输出生成的框分配给框寄存器(作为内部输出)时。

扩展的过程是瞎的:它不与产生外部输出的机制交互。这些是测量和放置机制,因此\setbox0=\hbox{hello}\the\wd0,在通过外部输出机制进行宽度计算时,示例必然是不可扩展的,因为宽度在输入级别不存在。(碰巧,它还必须通过内部输出机制来保存框,但这仅由禁止您编写的 TeX 设计决定\the\wd\hbox{hello}。)

现在,你要求将排版后的“最终结果”作为输入。我认为你的意思是:

你想绕过扩展的限制内部输出,即您希望\def\foo{hello}\foo将其转换为hello。这听起来像是一个合理的要求,但正如 David Carlisle 所说,这根本是不可能的。TeX 指定\def为不可扩展的,就是这样。有人想知道(我曾经想知道)为什么\def不像定义那样“扩展”为零,我也不知道原因,但事实是,就目前情况而言,它通过了内部输出机制,因此不会直接操作输入流。

如果我可以再哲学化一点的话,你所要求的是一种常见的“做错事”的方式。你想要发布一个编程结构,从而进一步输入这也是一种有效的编程结构,用于生产外部输出。由于这两种语言虽然相互交织,但实际上却不同,所以这是不可能的。这并不意味着没有等效的方法来书写你的输入构造,以便它创建您想要的内容。一种常见的解决方法是做很多不可扩展的事情,反复构建一个宏\result,然后其内容在之后可扩展(实际上,这就是前面的示例所做的)。

答案3

也许 David 的回答可以通过非 TeX 的观点来补充。

由于 TeX 宏处理与编程语言相当,因此可扩展与不可扩展的概念确实存在于计算机科学中,但措辞不同。

本质上,一个完全可扩展的宏或函数是纯函数,即一个没有副作用的函数,它不依赖于除其参数之外的任何东西。在伪 C 中:

int twice(int x)
{
    return 2*x;
}

对于给定的字符串,无论我们是否用其返回值替换函数,输出都不会改变:twice(4) 等于 8。现在我们采用稍微修改过的函数,最终得到一个本质上不可扩展的东西:

int twice_current_page()
{
    return 2*current_page;
}

文档中无法替换/扩展 twice_current_page()。获取结果的唯一方法是执行到此处的所有内容,然后执行函数体。

答案4

我认为这里的大部分混乱源于这样一个事实:当最初的提问者说“执行给定宏的完整结果”时,他可能并没有真正考虑到 TeX 中使用的 expandable 的技术含义。大多数答案似乎都假设他指的是 TeX 中的完整宏扩展,但我认为他指的是更类似于该宏对 TeX 状态的影响的东西。换句话说,问题是为什么我们不能指定一些更高级的中间格式来指定完全的TeX 系统上任何宏的效果,但不受任何宏扩展的影响(例如是原始递归,因此不是图灵完备的)

从理论上讲,这当然是可能的,因为可以借用函数式编程中避免副作用的技巧,编写一个递归函数式 TeX 求值器,该求值器使用 monad 来捕获宏(重新)定义和 TeX 基元的任何其他效果。因此,可以将标记序列的“完整求值”(前提是它们正确括号平衡,并且在扩展时不会以 ifx/expandafter/etc.. 结尾或跟随 expandafter)与在评估该序列或其扩展结果期间执行的 monad 转换器序列进行识别。如果想要非常挑剔,我们可以要求这些 monad 转换器和最终转换为 pdf 都是原始递归的,以确保它们不能隐藏其中的宏扩展。

现在我们有了存在的证明,更实际的是,人们可以尝试简单地坐下来,将 TeX 解释器的所有操作分解为一小组指令,这些指令在执行期间修改程序状态,并简单地保存在评估某个标记序列期间执行的指令列表以获得所需的概念。

相关内容