首先

首先

我发现使用\csname...\endcsname将定义...\relax如果...未定义,并且没有没有这种副作用的改进原语,这很烦人,尽管有部分解决方法(如先前在另一个问题)。

我可能错了,但我目前认为\relax副作用的唯一用途是在 e-TeX 和 出现之前测试命令是否未定义\ifdefined。显然,我们现在可能会被这种副作用所困扰,因为会有很多代码依赖它,但有人知道这种副作用的其他用途吗?将来可能会引入另一个没有这种副作用的原语吗?


接受后更新:

(非常感谢大家的回答和评论,我从每个人身上都学到了一些东西,所以我很高兴问了这个问题!)

请注意,我接受了 Bruno 的回答,因为他向我展示了副作用的一些好用途。如果你迟到了这个问题,你可能会发现按时间顺序阅读答案会更清楚(David 的、Joseph 的,然后是 Bruno 的)。此外,我仍然对你发现/记得/发明的任何其他用途感兴趣,或者是否有人可以更肯定地说出最初的意图是什么。

摘要(如果我做得不好,请原谅,请随意编辑或删除):

  • 总体感觉副作用并不是什么坏事;
  • David 指出,副作用对于进行 pre-eTeX\ifdefined测试来说不是必需的;
  • 约瑟夫(和弗兰克)提到了内存和性能问题,并表明副作用显然是故意的设计决定,尽管来源中没有具体说明做出该决定的原因tex.web
  • Bruno 展示了副作用的三种非常巧妙的用法,进一步增加了我对 LaTeX3 的兴趣!

答案1

我利用\relax的副作用\csname ...\endcsname有三个目的。

首先

与 LaTeX 在 中的使用方式类似\end{...}。我们将 TeXu 称为 TeX 的一个变体,其中\csname ...\endcsname未定义的控制序列未定义。

我们有一个按键列表,以及相关操作(任意 TeX 代码),我们想要定义\act{#1}执行按键标记的操作#1。首先定义各种操作。

\def\actA{\message{I found A.}}
\def\actB{\message{Oh, here is a B!}}
\def\actKEY{\message{The key was KEY.}}

如果你希望\act在未定义的键上不产生错误,你可以这样做

% in TeX
\def\act#1{\csname act#1\endcsname}
% in TeXu
\def\act#1%
  {%
    \expandafter\ifx\csname act#1\endcsname\undefined
    \else
      \csname act#1\expandafter\endcsname
    \fi
  }

现在,您可能会认为,出现错误会更好,因为我们想要捕捉键名中的拼写错误。那么在这种情况下,TeX 和 TeXu 相比如何?好吧,\def\act#1{\csname #1\endcsname}在 TeXu 中可以。但是,Undefined control sequence \abcKEYZ真的是最好的错误消息吗?为了获得更好的错误消息,我们可以这样做

% in TeX
\def\act#1%
  {%
    \expandafter\ifx\csname act#1\endcsname\relax
      \errmessage{Unknwon key #1}%
    \else
      \csname act#1\expandafter\endcsname
    \fi
  }
% in TeXu, same, with `\relax` replaced by `\undefined`.

然而,使用 TeX 我们可以做一些更快的事情:

% in TeX
\def\actA#1#2{\message{I found A.}}
\def\actB#1#2{\message{Oh, here is a B!}}
\def\actKEY#1#2{\message{The key was KEY.}}
\def\act#1%
  {%
    \csname act#1\endcsname
      \errmessage{Unknown key #1}%
  }

如果键已知,该\act...函数将删除错误消息,如果键未知,则未定义\csname构造变为\relax,并显示错误消息。TeXu 没有等效构造。在撰写本文时,此构造用于l3kernelfor\prg_new_conditional:Npnn和相关函数(其中有效键是条件形式TFTFp)和 for \int_compare:nTF(其中有效键是比较运算符<=>)。

第二

用于在可扩展设置中报告特殊情况。我在 LaTeX3 浮点模块l3fp(在包中找到l3kernel)和字符串转换l3str(在l3experimental包中)中使用它。

让我们看看l3fp\fp_to_tl:n {#1}评估浮点表达式#1并将其转换为可扩展的标记列表(例如0.123-1.4e-789-inf)。例如,在绘制图形时,我们只想\fp_to_tl:n { 1/0 }给出inf,但在其他情况下,这会触发错误。我最近添加了一个实验性功能(未完全实现),人们可以测试在计算过程中是否发生除以零或其他特殊情况。如果我们进行可扩展的计算,则true在出现错误时将开关设置为 将是微不足道的。在可扩展设置中,这是不可能的,我们唯一能做的就是使用\relax的副作用\csname...\endcsname。也就是说,当在计算中发生这样的除以零时,特定的控制序列将变成\relax,用户可以在计算结束后(或者更狡猾地,在计算本身内……)查询该控制序列。

在 中l3str,我希望能够将可能非常长的字符串从一种编码转换为另一种编码。这不能通过转换字符并将它们逐个存储在标记列表(无参数宏)中来实现:因为每次添加所需的时间与迄今为止转换的字符数成正比,总时间会变成字符串长度的二次方,通常太长了。相反,字符串会一次性转换。例如,假设我希望将一串字符转换为它们的字符代码列表(十进制表示),以逗号分隔。

\def\to@charcodes#1%
  {\expandafter\to@charcodes@\detokenize{#1}{\fi\iffalse\iffalse}\fi\fi}
\def\to@charcodes@#1%
  {\iffalse#1\fi\number`#1,\to@charcodes@}
\message{\to@charcodes{ABC}}

可以解决问题。现在,在转换中,输入字符串大多数时候可能无效。假设在我们的示例中,如果A字符串中出现,我们希望出现警告。然后我们只需将 添加\if#1A\flag@on\fi\to@charcode,并使用\def\flag@on{\expandafter\iffalse\csname some@flag\endcsname\fi}。要获得警告:

\ifx\some@flag\relax
  \message{Warning: backslash detected!}%
\fi

第三

是生成可扩展的伪随机数,而无需使用依赖引擎的原语(参见l3rand中速度极慢的l3trial命令,例如,在 GitHub repo 上找到)。主要的困难是让命令在每次调用时都以不同的方式展开。这可以通过结合和\csname...\endcsnameeTeX 的来实现\ifcsname。例如,我们可以定义一个函数来计算它被调用的次数:

\def\howmanytimes{\howmanytimes@0;}
\def\howmanytimes@#1;%
  {%
    \ifcsname howmanytimes@#1\endcsname
      \expandafter\howmanytimes@\number\numexpr 1+%
    \else
      \expandafter\howmanytimes@end
    \fi
    #1;%
  }
\def\howmanytimes@end#1;%
  {%
    \expandafter\iffalse\csname howmanytimes@#1\endcsname\fi
    #1%
  }
\message{\howmanytimes}
\message{\howmanytimes}
\message{\howmanytimes}
\message{\howmanytimes}

印刷0 1 2 3

答案2

我不确定曾经有一个使用:如果 TeX 未定义标记,则经典的未定义测试可能会

\expandafter\ifx\csname#1\endcsname\@undefined

而不是

\expandafter\ifx\csname#1\endcsname\relax

这样会更清楚一些。

另一方面,很难找到真正造成任何伤害的情况。 Joseph 在链接的问题中给出了一个可扩展的 e-tex 版本,可确保在仅扩展的上下文中出错。 如果您不介意离开一个{}组(或\begingroup\endgroup多个组),那么您可以使用

{\expandafter\aftergroup\csname#1\endcsname}

这只会将新创建的标记留在组之外(并且任何定义都会\relax在组端被丢弃)。

因此,唯一\csname-that-doesnt-let-to-relax有用的地方是当您处于仅扩展的上下文中,但希望在未先前定义该标记的情况下 TeX 抛出错误并且不完成扩展时。

答案3

虽然只有 Knuth 可以对他在这里采用的方法给出明确的答案,但值得注意的是,这种行为确实适用于 TeX 的其他部分,在这些部分中,将事物视为相等\relax(例如\noexpand)。这也意味着您可以使用

\csname foo\endcsname

无需事先进行任何形式的测试。在编写 TeX 时,需要密切关注性能,并且必须

\ifcsname foo\endcsname
  \csname foo\expandafter\endcsname
\fi

几乎肯定会成为一个问题(更多令牌、更多 csname 构建,...)。当然,现在不适用,但我想您可能会争辩说,在可读性和正确处理结束条件方面,必须进行两阶段构造仍然更加尴尬。


tex.web似乎所有这一切都是

@ @<Manufacture a control...@>=
begin r:=get_avail; p:=r; {head of the list of characters}
repeat get_x_token;
if cur_cs=0 then store_new_token(cur_tok);
until cur_cs<>0;
if cur_cmd<>end_cs_name then @<Complain about missing \.{\\endcsname}@>;
@<Look up the characters of list |r| in the hash table, and set |cur_cs|@>;
flush_list(r);
if eq_type(cur_cs)=undefined_cs then
  begin eq_define(cur_cs,relax,256); {N.B.: The |save_stack| might change}
  end; {the control sequence will now match `\.{\\relax}'}
cur_tok:=cur_cs+cs_token_flag; back_input;
end

除了结果之外没有透露太多信息。

相关内容