与保存堆栈条目相关的本地组中全局定义的语义差异

与保存堆栈条目相关的本地组中全局定义的语义差异

这是关于\csname 的 \global 变体…\endcsname,它要求对那里解释的问题提供解决方案,但不要求解释。

问题的要点是

{ \gdef\foo{...} }

似乎表现得不同

{ \expandafter\gdef\csname foo\endcsname{...} }

后者“在 save_stack 上添加了一个保留条目”。

所以我想知道:

  1. 为什么这两个构造的行为会有差异?这仅仅是实现方式的副作用\csname...\endcsname,还是故意造成的语义差异(就目前所知)。
  2. 链接问题中提到的日志文件中的条目到底是什么{retaining ...}意思?为什么在小组关闭后还需要保留本地定义?

答案1

谢谢你问这个问题。我不太明白这个例子另一个问题当我读到它时,但这个问题促使我再次看它,我想我现在明白了。

基本上,从这个例子中不清楚的是,当我们\expandafter\gdef\csname foo\endcsname{...}相同的组(或嵌套在该组内的组),即保存堆栈仅在组内增长。在退出组时(弹出堆栈时),会打印“retaining…”消息,但此消息本身不是问题;它只是堆栈之前已增长的证据。

那段话可能有点让人困惑,所以让我们从头开始理解保存堆栈。:-)


1.考虑这个例子:

\def\a{hello}
{
    \def\a{world}
}

这里,当内部组中的 TeX 发现\a被重新定义为时world,它会将前一个值(对包含 的标记列表的引用hello)保存到保存堆栈中。然后,当它到达组的末尾时,它会弹出堆栈以恢复 的定义hello。这是保存堆栈的明显原因,以下是相应的跟踪输出,假设您有\tracingrestores=2\tracinggroups=2以及 并且\tracingassigns=2内部组从第 10 行开始(例如)——我还修改了输出以删除 之前的换行符{into…}

{changing \a=undefined}{into \a=macro:->hello}
{entering simple group (level 1) at line 10}
{changing \a=macro:->hello}{into \a=macro:->world}
{restoring \a=macro:->hello}
{leaving simple group (level 1) entered at line 10}

2.现在考虑同样的例子,没有顶部\def\a{hello}

{
    \def\a{world}
}

— 再次,当 TeX 看到\def\a{world}组内时,它必须保存 的先前含义\a。它恰好是未定义的,但我们仍然需要在离开组后再次将其变为未定义,因此 TeX 需要将“未定义”含义放入保存堆栈。跟踪输出为:

{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=macro:->world}
{restoring \a=undefined}
{leaving simple group (level 1) entered at line 10}

3.现在考虑一个类似的例子:

{
  \let\a=\relax
  \gdef\a{world}
}

这里,当 TeX 看到第一个 时\let\a=\relax,它必须将先前的含义(“未定义”)保存到保存堆栈中,就像前面的示例一样。然后当它看到 时,\gdef它不必将任何东西放入保存堆栈中。最后,当它到达组的末尾并开始弹出其堆栈(当前包含 的“未定义”含义\a)时,它会注意到\a现在具有全局定义,因此它会忽略“未定义”含义并保留全局定义。这解释了跟踪输出:

{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=\relax}
{globally changing \a=\relax}{into \a=macro:->world}
{retaining \a=macro:->world}
{leaving simple group (level 1) entered at line 10}

4.最后,考虑这个例子:

{
  \expandafter\gdef\csname a\endcsname{world}
}

结果与前一种情况完全相同。这是因为当 TeX 看到 时,它\expandafter会暂时忽略\gdef并开始对下一个标记 ( \csname) 进行操作 — 这意味着如果尚未定义,则对\csname a\endcsname创建宏进行操作\a,并使用 的定义(这就是工作原理),并且只有在那之后它才会对 进行操作(先前暂时忽略)并重新定义为后面的新定义 ( )。因此跟踪输出与上次相同:\relax\csname\gdef\aworld

{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=\relax}
{globally changing \a=\relax}{into \a=macro:->world}
{retaining \a=macro:->world}
{leaving simple group (level 1) entered at line 10}

这就是全部内容了。回答您的具体问题:

  • \csname … \endcsname(1) 这只是实现方式的副作用;也就是说,它\let是第一个标记\relax。然而,这有据可查,因此可以说也是语义的一部分(正如每个人所期望的那样)。

  • (2a){retaining ...}日志文件中的条目表示(参见第 301 页TeXbook较早\relax放在保存堆栈上的定义,即 在被设置之前宏的值无论是什么\csname … \endcsname,都由于 而被忽略\gdef(并且全局定义被保留)。

  • (2b)“为什么在小组关闭后还需要保留本地定义?”——不是,它们也没有被保留。相反,你看到的是什么时候关闭组时,由于在同一组中进行了局部定义,所以之前保存的所有定义现在都会被检查,如果在任何时候存在全局定义,现在只会被丢弃。在组的末尾,保存堆栈将为空(或者更确切地说,与进入组时的大小相同)。


更具体地说,这是问题和解决方案的一个例子那个问题提问者在一个组内定义了很多宏(本质上),其方式大致相当于:

{
    \expandafter\gdef\csname A\endcsname{I'm A}
    \expandafter\gdef\csname B\endcsname{I'm B}
    \expandafter\gdef\csname C\endcsname{I'm C}
}

等等。正如我们在上面的示例 3 和 4 中看到的,这相当于:

{
    \let\A=\relax \gdef\A{I'm A}
    \let\B=\relax \gdef\B{I'm B}
    \let\C=\relax \gdef\C{I'm C}
}

等等。因此,上面的每个定义都会将一个条目放在保存堆栈上(因为控制序列名称之前的含义是by \let),并且只有在组末尾才会弹出所有这些条目。因此,如果有太多这样的定义;您将用尽“保存大小”。\relax\csname ... \endcsname

第一个发布的答案(由 Steven B. Segletes 撰写) 建议执行相当于\csname … \endcsname在最顶层执行的操作(其中不会将任何内容放在保存堆栈上)。

第二个发布的答案(由 Marcel Krüger 撰写) 建议做相当于:

{
  \begingroup\expandafter\endgroup\expandafter\gdef\csname A\endcsname{I'm A}
  \begingroup\expandafter\endgroup\expandafter\gdef\csname B\endcsname{I'm B}
  \begingroup\expandafter\endgroup\expandafter\gdef\csname C\endcsname{I'm C}
}

定义发生在立即退出的组内,因此每个堆栈都会立即弹出:跟踪输出为(模数换行符):

{entering simple group (level 1) at line 10}

{entering semi simple group (level 2) at line 11}
{changing \A=undefined}{into \A=\relax}
{restoring \A=undefined}
{leaving semi simple group (level 2) entered at line 11}
{globally changing \A=undefined}{into \A=macro:->I'm A}

{entering semi simple group (level 2) at line 12}
{changing \B=undefined}{into \B=\relax}
{restoring \B=undefined}
{leaving semi simple group (level 2) entered at line 12}
{globally changing \B=undefined}{into \B=macro:->I'm B}

{entering semi simple group (level 2) at line 13}
{changing \C=undefined}{into \C=\relax}
{restoring \C=undefined}
{leaving semi simple group (level 2) entered at line 13}
{globally changing \C=undefined}{into \C=macro:->I'm C}

{leaving simple group (level 1) entered at line 10}

如果您不关心可扩展性等,而只是想了解保存堆栈,那么建议的解决方案如下:

{
    {\let\A=\relax} \gdef\A{I'm A}
    {\let\B=\relax} \gdef\B{I'm B}
    {\let\C=\relax} \gdef\C{I'm C}
}

所以您可以明白为什么保存堆栈不会持续增长。

相关内容