我离开有一段时间了,我忘了这种问题是否合适,但我还是想问一下。最近我写了一些代码(在,expl3
但我希望它足够清晰),回想起来,我对它的风格感到疑惑。代码片段后面的一些评论(略作改编自unicode-math
):
\cs_new:Nn \um_if_char_spec:nNNT
{
% case 1:
\seq_if_in:NnT \l_um_mclass_range_seq {#3} { \use_none_delimit_by_q_nil:w }
% case 2:
\seq_if_in:NnT \l_um_cmd_range_seq {#2} { \use_none_delimit_by_q_nil:w }
% case 3:
\seq_map_inline:Nn \l_um_char_range_seq
{
\um_int_if_slot_in_range:nnT {#1} {##1}
{ \seq_map_break:n { \use_none_delimit_by_q_nil:w } }
}
% this executes if no match was found:
\use_none:nnn
\q_nil
\use:n
{
\clist_put_right:Nx \l_um_char_num_range_clist { \int_eval:n {#1} }
#4
}
}
这个想法是,三种可能性可以导致匹配并执行一些额外的“真”代码。检查这些匹配可能很耗时,因此任何真发生都应立即跳到末尾。这是通过\use_none_delimit_by_q_nil:w
跳过标记\q_nil
并忽略其路径上的所有内容来实现的。此时它执行“真”代码。
这通常可以通过一组嵌套条件来完成,如下所示:
iftrue-(code)-else-(iftrue-(code)-else-(iftrue-(code))))
但我想我并不想多次写出该(code)
部分——这看起来不够优雅而且容易出错。那么你怎么看?这段代码丑吗?你会怎么写呢?
答案1
我不确定最简洁的编码方式是什么,但这里有一个建议。正如您所说,第四个参数是“真实代码”。这让我想到了条件。但是,您更喜欢避免嵌套条件。为此,我们需要一种方法来跳过标记直到结束标记,在那里我们返回 true 或 false。
\RequirePackage{expl3}
\ExplSyntaxOn
\cs_new_protected:Npn \um_if_char_spec:nNNT #1#2#3#4
{
\um_if_char_spec_aux:nNNT {#1} #2 #3
{
\clist_put_right:Nx \l_um_char_num_range_clist { \int_eval:n {#1} }
#4
}
}
\cs_new:Npn \um_break_true: #1 \um_break_point: { \prg_return_true: }
\cs_new:Npn \um_break_false: #1 \um_break_point: { \prg_return_false: }
\prg_new_protected_conditional:Nnn \um_if_char_spec_aux:nNN { T }
{
% case 1:
\seq_if_in:NnT \l_um_mclass_range_seq {#3} { \um_break_true: }
% case 2:
\seq_if_in:NnT \l_um_cmd_range_seq {#2} { \um_break_true: }
% case 3:
\seq_map_inline:Nn \l_um_char_range_seq
{
\um_int_if_slot_in_range:nnT {#1} {##1}
{ \seq_map_break:n { \um_break_true: } }
}
% else:
\um_break_false:
\um_break_point:
}
\q_nil
我们可以像您一样简单地使用并定义,而不必使用自定义的“break_point”标记
\cs_new:Npn \um_break_true: { \use_i_delimit_by_q_nil:nw { \prg_return_true: } }
\cs_new:Npn \um_break_false: { \use_i_delimit_by_q_nil:nw { \prg_return_false: } }
编辑:在查看了 的代码后unicode-math
,我认为您使用的许多序列最好以标记列表的形式实现。也就是说,一个序列,其项都是单个标记,并且您关心的主要操作是\seq_if_in:NnTF
,应该是标记列表:搜索操作会快得多,尽管映射会变得稍慢。
鉴于的第二个和第三个参数\um_if_char_spec:nNNT
是 N 类型参数(参见签名),我推测\l_um_mclass_range_seq
和\l_um_cmd_range_seq
可以实现为单个标记列表。[我还注意到,在包的其他地方,您对 N 与 n 参数的处理有点马虎,将带括号的参数提供给 N 类型参数。]
答案2
是的
答案3
是的。
考虑确定字母A,b,C, 是在希腊字母、拉丁字母或法语字母。使用您建议的解决方案,您将需要编码 74 个 case 语句。
使用列表,连接序列并且只进行一次测试。
\documentclass{book}
\usepackage{lipsum,graphicx}
\begin{document}
\makeatletter
\def\IfIn#1#2{%
\def\check##1{%
\newif\ifin@
\in@{##1}{#2}
\ifin@ True Action
\else
False Action
\fi}
\@for \i:=#1\do{%
\expandafter\check\i%
}}
\IfIn{a,b,c,\delta,\gamma,}{a,b,c,d,\beta,\delta}
\makeatother
\end{document}
至于符号,数学中正确的符号对于代码来说也是正确的,我将引用哈尔莫斯。
只要有可能避免使用复杂的字母设备,就尽量避免它。
我个人认为 LaTeX3 团队做得很好,但我发现很难理解诸如 这样的符号 \um_if_char_spec_aux:nNNT
。我只能说,美丽的 Pascal 嫁给了 TeX,但他们的孩子却很丑陋!
答案4
如果您想要的是意见(回答您的第一个问题),这里有一个......
从编程的角度来看,我认为使用跳转而不是嵌套 if 进行编码几乎没有区别。执行时间应该是可比的。但是,由于我不熟悉expl3
代码,我不确定它是如何\use_none_delimit_by_q_nil:w
工作的。如果它必须扫描标记直到找到,\q_nil
那么它可能不是真正的“跳转”,因此可能比使用宏定义code
(本质上更像“跳转”)更慢。如果code
是紧凑的,那么扫描它应该不是什么大问题。
我猜我的总体意见是根据 的大小/跨度来总结的code
。如果code
很大,那么就把它放在宏中,这样可以压缩外观并提高代码的可读性。如果code
很小,那么就按原样包含它。
这大概相当于 1.5 美分的投入