16

Question:

For a macro that has only one parameter, are there any issues with defining it as follows:

\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}%
}%

Note that the first three parameters are unused and are intended for possible future enhancements.

Notes:

  • The example usage here is just for illustrative purposes. This is for more complex macros where enhancements which were not obvious at the time of creation may be needed in the future.

  • I initially tried defining macros to hold each of the parameters as:

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

but found that this has its own issues (see Reference) if the macro that called this macro had used the same name, so have moved away from this approach.

References:

Background:

In the process of developing my macros, I noticed that I tend to start with something like:

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

The I later realize that I want to add an optional parameter:

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

where I have to locate all the occurrences of #1, replace them with #2, which isn't too difficult, but gets more difficult with more mandatory parameters.

Then later, I realize that I want to define a starred variant (or have more than one optional parameter) at which time I switch to using the 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}%
        }%
    }%
}%

Code:

\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}
7

Two (or more) optional arguments have an obvious problem: you can't specify the second argument without specifying also the first one.

As an example, consider \makebox: you can do \makebox[3em]{text} or \makebox[3em][l]{text} and the second optional argument doesn't make sense if the width is not specified. A second example is \textcite of biblatex that has the syntax

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

and \textcite[abc]{xyz} is interpreted with abc being the postnote, that is, the probably most common case; if a prenote is wanted, instead, one has to type \textcite[abc][]{xyz} and the macro must check whether the second optional argument is empty.

The situation becomes really involved when three optional arguments are allowed:

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

but here, again, the third argument makes sense only if the first two are specified; actually, specifying the height argument requires the "outer alignment" argument to be present.

A key-value syntax for options is surely better when more than one of them is needed, but poses a problem when the macro is expected to be used nested. The problem is exactly what you mentioned: when you type something like

color=red

as key-value pair, some control sequence will be defined, say

\def\MyMacro@color{red}

(the actual implementation varies with the key-value package you're using). This can be a concern or not depending on the working of the macro; for instance a key-value syntax for \parbox would not be a problem, because eventually \parbox makes a \vbox (or \vtop or \vcenter) that makes a group by itself, so assignments made inside it will disappear when the inner \parbox is finished up.

Therefore, granted that key-value seems the best approach, you must ensure that nested calls don't overwrite values, by using implicit (box making primitives or macros based on them) or explicit (\begingroup and \endgroup) grouping and restoring the defaults before evaluating the keys.

Let me make an example; you want to use an optional label with default value "foo" and a color with default value "black" for it. I'll use l3key syntax.

\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

Not restoring the defaults like before, but setting

\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,
 }

would not help when \MyMacro is called nested inside another call of \MyMacro that has set label or color, because in the following case

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

the red value passed to the outer call would be inherited in the inner call.

However, in some cases, inheritance might be wanted, say for a font selection. If all values should be inherited, then no resetting of the defaults is needed; however, a grouping is still necessary for avoiding that a call to \MyMacro will influence all subsequent (non nested) ones.

  • 1
    I'd add to your very last point that such inheritance may be a good things in some cases. E.g., a hypothetical \fontchange[shape=it]{...\fontchange[series=bf]{...}}. – Bruno Le Floch May 19 '13 at 10:50
  • @BrunoLeFloch I was thinking the same thing. :) – egreg May 19 '13 at 10:52
  • Another approach for nesting to work would be to pass the values of the keys to a code-level function as arguments: \keys_set:nn { grill/mymacro } { ... } \__mymacro:VV \l_grill_mymacro_label_tl \l_grill_mymacro_color_tl. Of course, this loses the unlimitedness of the key--value syntax. – Bruno Le Floch May 19 '13 at 10:53
  • For the issue of specifying the second optional parameter without specifying the first, my macros treat \MyMacro[][<second optional param>] as meaning use the default for the first optional parameter of \MyMacro. – Peter Grill May 19 '13 at 17:21
21

There are of course some time considerations in looking for the arguments that are not there but they may not be measurable on modern machines.

However I wouldn't define a command with a star form, two optional arguments and a mandatory argument. If you need that many you will probably find yourself needing more so the plan of using #4 and not changing it may fail anyway.

key=value arguments were introduced in large part to solve this. If you define

\mymacro[]{hello}

you can later add

\mymacro[color=red]{hello}

or

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

without having to re-index the definition (or break existing documents by having incompatible argument forms).

  • It may be worth having a star variant also, despite letting the optional argument take key values. – Ryan Reich May 19 '13 at 16:36
6

As an addition to David Carlisle's answer, I want to add a better way to shift the argument from #1 to #4 with dummy arguments #1, #2, #3. Unless you already know what syntax you will want for your final version, you should put arguments #1, #2 and #3 that are unlikely to appear

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

Here I picked an optional period, as people are unlikely to write \MyMacro.{Hi!} (although you should consider the case of someone writing \MyMacro. thinking \MyMacro{.}). If you know what syntax you will want, then rather than leaving arguments unused it is better to include code that says "Hey, I haven't implemented that yet, sorry!" for users who happen to try hat syntax (besides, we might add a \showxparsecommand that would show the function's syntax, so users may wish to try).

\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}
          }
      }
  }
  • Great idea. I normally do include error messages in other such situations for unexpected invocations, but for some reason it did not occur to me for this. – Peter Grill May 19 '13 at 17:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.