我写的这个分支代码是不是有点混乱?

我写的这个分支代码是不是有点混乱?

我离开有一段时间了,我忘了这种问题是否合适,但我还是想问一下。最近我写了一些代码(在,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

是的

考虑确定字母AbC, 是希腊字母、拉丁字母或法语字母。使用您建议的解决方案,您将需要编码 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 美分的投入

相关内容