Creating new document commands in LaTeX has traditionally been the job of
\newcommand
. This lets you create command with mandatory arguments, and can
also add a first optional argument. However, it can’t create more complex
commands: LaTeX uses for example stars, multiple optional arguments, etc. To
create these, the kernel itself uses lower-level TeX programming. But this is
opaque to many users, and a variety of packages have been created to ease the
burden.
Over the last decade, the LaTeX team have developed
xparse
, a generic document command parser, as a
way to unify many ideas and provide a single consistent way to create document
commands. Most of that code will soon be moving to the LaTeX kernel itself, and
I’ve provided some
ideas
about how best to exploit that in a recent post.
Here, I want to look at a related issue: why use the xparse
approach, and how
it compares to existing solutions, both in the LaTeX kernel and the wider
package sphere. Here, I’m going to avoid talking about ‘simple’ shortcuts
(things like \newcommand\myname{Joseph Wright}
): these are best left to
\newcommand
. Instead, I want to deal with commands which take arguments and
have some element of ‘programming’ to them.
What I’ll seek to highlight here is that using \NewDocumentCommand
, we get
a single consistent and reliable way to create a variety of commands. There’s
no need to worry about clashes between approaches, and it all ‘just works’.
Preliminaries: protected commands and optional arguments
Before we start, a couple of things are worth mentioning. First, there is the
idea of ‘protected’ commands. In some places, we need commands not to ‘expand’
(turn into their definition). With a modern TeX system, that can be arranged by
the engine itself (pdfTeX or similar), using ‘e-TeX’ and the \protected
primitive (built-in). The LaTeX kernel doesn’t use that mechanism in
\newcommand
, but lots of other tools do. I’m going to assume that we want to
make protected commands unless I mention otherwise. Almost always, unless you
are creating a ‘shortcut’ for some text, you want your commands to be protected.
The second thing to note is that TeX itself has no concept of optional
arguments, so they are arranged using some clever look-ahead code. In xparse
,
nested optional arguments are handled automatically, but again, \newcommand
and similar do not do that.
The kernel: versus \newcommand
The kernel’s \newcommand
can, as I’ve said, create commands with multiple
mandatory arguments but only with one optional one. As a simple example, we
might have
\newcommand\foo[3][default]{%
Code perhaps using #1 and definitely using #2 and #3%
}
We can of course do the same using \NewDocumentCommand
\NewDocumentCommand\foo{+O{default} +m +m}{%
Code perhaps using #1 and definitely using #2 and #3%
}
You’ll notice that I’ve use +m
for the mandatory arguments, as that matches
\newcommand
: the arguments can accept paragraphs. With \newcommand
, all
arguments either accept \par
or do not: with \NewDocumentCommand
we can
select on a per-argument level what happens.
The optional argument with a default works using O{default}
, and the result
will be the same functionality. We gain the idea that nested optional arguments
are parsed properly, some better error messages if we use \foo
incorrectly,
and an engine-robust definition of \foo
.
We can’t do a lot more with \newcommand
, so rather than try to show off other
\NewDocumentCommand
features here, we’ll first consider how we might make more
complex syntaxes using just the classical LaTeX kernel.
The kernel: versus \def
Using the TeX primitive \def
, plus the kernel internal commands \@ifstar
and
\@ifnextchar
, we can construct more complex syntaxes. For example, let’s
create the syntax for \section
: a star, and optional argument and a mandatory
one. I’ll assume we are have @
as a letter here. I’m also going to pass the
presence of a star as the text true
or false
, as it makes things clearer.
\newcommand\section{%
\@ifstar
{\section@auxi{true}}
{\section@auxi{false}}%
}
\def\section@auxi#1{%
\@ifnextchar[%]
{\section@auxii{#1}}
{\section@auxii{#1}[]}%
}
\long\def\section@auxii#1[#2]#3{%
% Here:
% #1 is "true"/"false" for a star
% #2 is the optional argument
% #3 is the mandatory argument
}
As you’ll see, this is a bit tricky already, and it doesn’t cover the case where we want to have the optional argument default to the mandatory one, when it’s not given. It also doesn’t allow for nested optional arguments, and it’s not engine-robust. We might of course use more complex paths for the star: we could have independent routes.
Using \NewDocumentCommand
, things are a lot easier
\NewDocumentCommand\section{s +O{#3} +m}{%
% Here:
% #1 is "true"/"false" for a star
% #2 is the optional argument
% #3 is the mandatory argument
}
The minor difference now is that #1
is a special token that we can test for
truth using IFBooleanTF
. I’ve also allowed for the optional argument picking
up the mandatory one, when it’s not given.
We could make more complex examples, but the bottom line remains the same:
using \NewDocumentCommand
, we are always going to have simple one-line
interface descriptions, and the behind-the-scenes TeX argument parsing
is all hidden away.
etoolbox
: versus \newrobustcmd
The etoolbox
package offers \newrobustcmd
as a complement to \newcommand
. It does exactly the same as \newcommand
,
except it uses e-TeX to make engine-protected commands. So from an interface
point-of-view, there’s nothing new here.
twoopt
: versus \newcommandtwoopt
The twoopt
package allows a syntax similar to
\newcommand
but for creating two optional arguments. We’ll take an example
from it’s documentation:
\newcommandtwoopt\bsp[3][AA][BB]{%
\typeout{\string\bsp: #1,#2,#3}%
}
This is reasonably clear: we have an optional argument #1
, and optional
argument #2
and a mandatory argument #3
. The two optional arguments each here
have a default.
How does that look with \NewDocumentCommand
?
\NewDocumentCommand\bsp{+O{AA} +O{BB} +m}
\typeout{\string\bsp: #1,#2,#3}%
}
You’ll see that we stay consistent here: the same syntax is used to create one,
two or even more optional arguments. I wouldn’t recommend using multiple
optional arguments in most cases, but when we do, it’s a lot easier using
\NewDocumentCommand
.
What twoopt
cannot do, but \NewDocumentCommand
can, is create optional
arguments that are not in the first/second positions. That would require either
the TeX coding we’ve already seen, or using a different tool again, if we were
using twoopt
.
suffix
: versus \withsuffix
The suffix
package allows one to extend an
existing command to look for an optional token (‘suffix’) immediately after the
command name. Taking a simple example from
StackExchange, we start with
\newcommand\foo{blah}
\WithSuffix\newcommand\foo*{blahblah}
which translates to
\NewDocumentCommand\foo{s}{%
\IFBooleanTF{#1}
{blah}
{blahblah}
}
This means we only need one line for the interface set up, and don’t need for
example to split up grabbing optional arguments into two different places
(back for example with \section
).
xargs
: versus \newcommandx
The xargs
package is perhaps the most complete
approach to extending \newcommand
as far as optional arguments are concerned.
It introduces \newcommandx
, which has the same syntax as \newcommand
but
where the second optional argument is a keyval list. That then described which
arguments are optional, and what their defaults are. Taking an example from the
documentation
\newcommandx*\coord[3][2=1,3=n]{(#2_{#1},\ldots,#2_{#3})}
would create a command with two optional arguments, #2
and #3
, leaving #1
mandatory. Translating into \NewDocumentCommand
syntax might make that
clearer!
\NewDocumentCommand\coord{m O{1} O{n}}{%
(#2_{#1},\ldots,#2_{#3})%
}
xargs
has the idea of usedefault
, which allows []
to be the same as
[default]
. That’s not something xparse
does, as it is pretty confusing: what
happens when you want an empty optional argument? This links to something I’ve
said before: avoid consecutive optional arguments unless the second is
dependent on the first.
newcommand
: versus newcommand.py
Stepping outside for TeX itself, Scott Pakin’s
newcommand.py
Python script provides a
description language somewhat like xparse
, and converts this into a ‘template’
of TeX code, allowing a ‘fill in the blanks’ approach to creating commands. It
can cover several of the ideas that xparse
can, including a few that will
not be migrated to the LaTeX kernel. It can also set up a command taking more
than 9 arguments, but that’s always going to be tricky as a user.
What is important is that using a script means we have to work in two steps, and it’s hard to see what’s happening from the TeX source. It also doesn’t offer anything that the kernel doesn’t already do: no protected commands, no nested optional arguments, no improved error messages. So in many ways this is simply techniques we’ve already seen, just made a little more accessible, at least if you have Python installed.
environ
: versus \NewEnviron
As well as document commands, the xparse
syntax can be used to create
document environments: the same relationship we have between \newcommand
and \newenvironment
. What people sometimes want to do is grab an entire
document environment body and use it like a command argument. Classically,
one does that using the environ
package. Again, taking an example from the documentation
\NewEnviron{test}{%
\fbox{\parbox{1.5cm}{\BODY}}\color{red}
\fbox{\parbox{1.5cm}{\BODY}}%
}
would grab all of the body of the environment test
and set it twice: the
body is saved as \BODY
.
Using \NewDocumentEnvironment
, we have a syntax similar to \newenvironment
\NewDocumentEnvironment{test}{+b}{%
\fbox{\parbox{1.5cm}{#1}}\color{red}
\fbox{\parbox{1.5cm}{#1}}%
}{}
with the argument grabbed in the normal way as (here) #1
. We can therefore
have ‘real’ arguments first, then grab the body.
Summary
Using the tools set up in \NewDocumentCommand
, we can have a consistent
way of creating a wide range of document commands. Rather than use a mixture
of tools, from the kernel, TeX itself and the package sphere, it is far
preferable to use the single interface of \NewDocumentCommand
for all
commands today.