定义可扩展宏

定义可扩展宏

问题:

对于具有仅有的一个参数,这样定义是否存在问题:

\NewDocumentCommand{\MyMacro}{%
    s%    #1 = starred variant            (*** unused as of yet ***)
    O{}%  #2 = optional parameter         (*** unused as of yet ***)
    O{}%  #3 = another optional parameter (*** unused as of yet ***)
    m%    #4 = the mandatory parameter
}{%
    \emph{#4}%
}%

请注意,前三个参数未使用,用于将来可能的增强。

笔记:

  • 此处的示例用法仅用于说明目的。这适用于更复杂的宏,在创建时可能不明显的增强功能在将来可能需要。

  • 我最初尝试定义宏来保存每个参数:

    \def\GivenText{#1}%
    \def\ColorToUse{#2}%
    

但发现如果调用此宏的宏使用相同的名称,则这有其自身的问题(参见参考),因此已经放弃了这种方法。

参考:

背景:

在开发宏的过程中,我注意到我倾向于从以下内容开始:

\newcommand{\MyMacro}[1]{\emph{#1}}%

后来我意识到我想添加一个可选参数:

\newcommand{\MyMacro}[2][black]{\textcolor{#1}{\emph{#2}}}%

我必须找到所有出现的#1,并将它们替换为#2,这并不是太难,但是随着强制参数的增多,难度会加大。

然后后来,我意识到我想定义一个带星号的变体(或者有多个可选参数),此时我切换到使用xparse's \NewDocumentCommand

\NewDocumentCommand{\MyMacro}{%
    s%         #1 = starred variant
    O{black}%  #2 = optional parameter
    m%         #3 = the mandatory parameter
}{%
    \textcolor{#2}{%
        \IfBooleanTF{#1}{%
            #3%
        }{%
            \emph{#3}%
        }%
    }%
}%

代码:

\documentclass{article}
\usepackage{xcolor}
\usepackage{xparse}

%\newcommand{\MyMacro}[1]{\emph{#1}}%  Version 1

%\newcommand{\MyMacro}[2][black]{\textcolor{#1}{\emph{#2}}}% Version 2

\NewDocumentCommand{\MyMacro}{%  Version 3
    s%         #1 = starred variant
    O{black}%  #2 = optional parameter
    m%         #3 = the mandatory parameter
}{%
    \textcolor{#2}{%
        \IfBooleanTF{#1}{%
            #3%
        }{%
            \emph{#3}%
        }%
    }%
}%



\begin{document}
Some \MyMacro*[red]{not so important text} and even less important text.
\end{document}

答案1

两个(或更多)可选参数有一个明显的问题:如果不指定第一个参数,就无法指定第二个参数。

举个例子,考虑一下\makebox:你可以执行\makebox[3em]{text}\makebox[3em][l]{text},如果未指定宽度,则第二个可选参数没有意义。第二个示例是语法\textcitebiblatex

\textcite[<prenote>][<postnote>]{<key>}

并被\textcite[abc]{xyz}解释abc后记,这可能是最常见的情况;如果需要预注,则必须输入\textcite[abc][]{xyz},并且宏必须检查第二个可选参数是否为空。

当允许三个可选参数时,情况就变得非常复杂:

\parbox[t][5\baselineskip][b]{text}

但在这里,第三个参数只有在指定前两个参数时才有意义;实际上,指定高度参数需要存在“外部对齐”论点。

当需要多个选项时,键值语法肯定更好,但当宏需要嵌套使用时,就会出现问题。问题正是您提到的:当您输入类似

color=red

作为键值对,将定义一些控制序列,例如

\def\MyMacro@color{red}

(实际的实现因您使用的键值包而异)。这可能是一个问题,也可能不是,这取决于宏的工作方式;例如,的键值语法\parbox不会成为问题,因为最终\parbox会生成一个\vbox(或\vtop\vcenter),它会自行生成一个组,因此当内部\parbox完成后,其中的分配将消失。

因此,假设键值似乎是最好的方法,必须\begingroup通过使用隐式(基于它们的框制作原语或宏)或显式(和\endgroup)分组,确保嵌套调用不会覆盖值在评估密钥之前恢复默认值。

让我举个例子;您想使用一个默认值为“foo”的可选标签和一个默认值为“black”的颜色。我将使用l3key语法。

\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\MyMacro}{om}
 {
  \grill_mymacro:nn { #1 } { #2 }
 }
\cs_new_protected:Npn \grill_mymacro:nn #1 #2
 {
  % start a group for avoiding bad effects in case of nesting
  \group_begin:
  \keys_set:nn { grill/mymacro }
   {
    % restore the defaults
    label,color,
    % evaluate the keys
    #1
   }
  % do the real job
  <whatever using #2 and
   \l_grill_mymacro_label_tl
   \l_grill_mymacro_color_tl>
  \group_end:
 }

\keys_define:nn { grill/mymacro }
 {
  label .tl_set:N  = \l_grill_mymacro_label_tl,
  label .default:n = foo,
  color .tl_set:N  = \l_grill_mymacro_color_tl,
  color .default:n = black,
 }
\ExplSyntaxOff

不像以前那样恢复默认设置,而是设置

\keys_define:nn { grill/mymacro }
 {
  label .tl_set:N  = \l_grill_mymacro_label_tl,
  label .initial:n = foo,
  color .tl_set:N  = \l_grill_mymacro_color_tl,
  label .initial:n = black,
 }

\MyMacro当被嵌套在\MyMacro设置了label或 的另一个 调用中时,不会有帮助color,因为在以下情况下

\MyMacro[color=red]{... \MyMacro[label=bar]{something} ...}

red传递给外部调用的值将在内部调用中被继承。

但是,在某些情况下,可能需要继承,例如字体选择。如果全部值应该被继承,那么就不需要重置默认值;然而,仍然需要进行分组,以避免调用\MyMacro影响所有后续(非嵌套)的调用。

答案2

当然,寻找不存在的参数时需要考虑一些时间,但它们可能无法在现代机器上测量。

但是我不会定义一个带有星号形式、两个可选参数和一个强制参数的命令。如果你需要那么多,你可能会发现自己需要更多,所以使用#4而不改变它的计划可能会失败。

引入 key=value 参数很大程度上是为了解决这个问题。如果你定义

\mymacro[]{hello}

您可以稍后添加

\mymacro[color=red]{hello}

或者

\mymacro[color=red, size=large]{hello}

而不必重新索引定义(或通过不兼容的参数形式破坏现有文档)。

答案3

作为对 David Carlisle 答案的补充,我想添加一种更好的方法,使用虚拟参数, , 将参数从 转移#1到。除非您已经知道最终版本需要什么语法,否则您应该将不太可能出现的参数,和放在一起#4#1#2#3#1#2#3

\NewDocumentCommand{\MyMacro}{t.t.t.m}{\emph{#4}}

这里我选择了一个可选句点,因为人们不太可能写\MyMacro.{Hi!}(尽管你应该考虑有人写\MyMacro.思考的\MyMacro{.}情况)。如果你知道你想要什么语法,那么与其让参数闲置,不如包含代码,告诉那些碰巧尝试 hat 语法的用户“嘿,我还没有实现它,抱歉!”(此外,我们可能会添加一个\showxparsecommand来显示函数的语法,这样用户可能希望尝试)。

\ExplSyntaxOn
\NewDocumentCommand{\MyMacro}{soom}
  {
    \IfBooleanTF {#1}
      { \msg_error:nnn { mypkg } { not-implemented } { \MyMacro* } }
      {
        \IfValueTF {#2}
          { \msg_error:nnn { mypkg } { not-implemented } { \MyMacro[] } }
          {
            % No need to check #3 since it can only have a value if #2 does.
            \emph{#4}
          }
      }
  }

相关内容