问题:
对于具有仅有的一个参数,这样定义是否存在问题:
\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}%
但发现如果调用此宏的宏使用相同的名称,则这有其自身的问题(参见参考),因此已经放弃了这种方法。
参考:
- 我这样做的原因的一个例子不是总是使用一个
\def
来保存参数的值定义 TikZ 常量的首选方法是什么?
背景:
在开发宏的过程中,我注意到我倾向于从以下内容开始:
\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}
,如果未指定宽度,则第二个可选参数没有意义。第二个示例是语法\textcite
为biblatex
\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}
}
}
}