我发现使用\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 没有等效构造。在撰写本文时,此构造用于l3kernel
for\prg_new_conditional:Npnn
和相关函数(其中有效键是条件形式T
、F
、TF
和p
)和 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...\endcsname
eTeX 的来实现\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
除了结果之外没有透露太多信息。