How to \let an xparse defined command?
In general the answer is do not do this. The idea behind xparse
is the document level commands (\example
) are there to define the syntax but should be implemented by use of documented code-level functions (so something like \module_command:nn
here). As such, any new or altered definitions should not pass 'document level' syntax around at all but should be defined using xparse
and the code-level functions the original uses
\NewDocumentCommand \example { o m }
{
\module_command:nn {#1} {#2} % Oops, doesn't cover \NoValue
}
...
\RenewDocumentCommand \example { o m }
{
\IfNoValueTF {#1}
{ \module_command:n {#2} } % Presumably different
{ \module_command:nn {#1} {#2} }
}
As such, the team quite deliberately don't provide \LetDocumentCommand
or similar.
The best practice for expl3
and xparse
use has developed over some time and as such some packages written by the team themselves still need revision to follow them. From my own code, notes2bib
is probably the 'model' package in terms of showing the correct approach. On the other hand, at the time of writing the release version of siunitx
needs revision to follow them: the code was largely written in parallel with expl3
and xparse
development. There is development ongoing to create a third version of siunitx
which will be implemented with this clear separation. (I hope to get this ready during 2016.)
If you need to create a new interface for an xparse
-defined command and it is built on non-documented code functions, at present you will have to use those
\RenewDocumentCommand \example { o m }
{
% To be revised once documented interfaces are available
\IfNoValueTF {#1}
{ \__module_command:n {#2} } % Presumably different
{ \__module_command:nn {#1} {#2} }
}
Most authors making use of expl3
are aware of this aim, but you might wish to check with them that such developments are ongoing.
Commands defined with \NewDocumentCommand
behave in an indirect way; the situation is similar to the one addressed by letltxmacro
when dealing with commands defined with \DeclareRobustCommand
, which is easier to describe.
When you do
\DeclareRobustCommand{\foo}[1]{...#1...}
LaTeX actually defines two macros, in a way that's essentially
\edef\foo{\noexpand\protect\expandafter\noexpand\csname foo \endcsname}
\expandafter\newcommand\csname foo \endcsname[1]{...#1...}
Note the trailing space in the name of the macro that does the real job. So, if you naively do
\let\origfoo\foo
\DeclareRobustCommand{\foo}[1]{\origfoo{#1}?}
the call \foo{x}
in normal text, where \protect
is \relax
, would give, in succession, (using •
to denote the trailing space in macro names having it)
\protect\foo•{x} % first level expansion
\foo•{x} % \relax disappears
\origfoo{x}? % replacement text of redefined \foo•
\protect\foo•{x}? % first level expansion of \origfoo
\foo•{x}? % \relax disappears
Yikes! Infinite loop!
If you try
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand \example { o m }
{
#1 ~ #2
}
\cs_show:N \example
the terminal output would be (reformatted for better reading)
> \example=\protected macro:->\int_zero:N \l__xparse_processor_int
\tl_set:Nn \l__xparse_args_tl {\example code }
\tl_set:Nn \l__xparse_fn_tl {\example }
\__xparse_grab_D:w []{-NoValue-}
\__xparse_grab_m_1:w \l__xparse_args_tl .
So when you do \let\origexample\example
you just make it the same as this macro, but a subsequent
\RenewDocumentCommand{\example}{...}{...}
would change the meaning of \example
and of \example•code
(again with a space in the name) which is actually the macro doing the real work. The same kind of infinite loop would arise, because the newly defined \example•code
macro will contain a call of \origexample
that will eventually call \example•code
. Infinite loop.
Building \LetDocumentCommand
is not conceptually difficult, nor it is adding such commands to the ones managed by xpatch
or regexpatch
, since they follow a common pattern just like \DeclareRobustCommand
.
However the situation is very different: the main reason is that the way macros defined with \NewDocumentCommand
work is not cast in stone. It could change abruptly if the team decides so: the usage of “private” functions with __
in their name means exactly that no package developer should rely on this particular implementation.
The situation with \DeclareRobustCommand
is different, because the LaTeX2e kernel publishes the interface (and indeed some packages exploiting this exist, not only letltxmacro
and xpatch
).
The reasons why \LetDocumentCommand
will not be provided by xparse
have been explained by Joseph Wright. You now have the tools for managing it, if you dare. I won't. ;-)
While the warnings of Joseph Wright and egreg that one shouldn't do this may be just as valid today (or may not be; I don't know), it seems that things may have changed in the almost five years since the previous two answers. In LaTeX News, issue 32, dated October 2020, on page 4, I note the following section (emphasis mine):
Provide a way to copy robust commands. . .
With the previous LaTeX2ε release, several user-level commands were made robust, so the need for a way to create copies of these commands (often to redefine them) increased, and the LaTeX2ε kernel didn’t have a way to do so. Previously this functionality was provided in part by Heiko Oberdiek’s letltxmacro package, which allows a robust command
\foo
to be copied to\bar
with\LetLtxMacro\bar\foo
.From this release onwards, the LaTeX2ε kernel provides
\NewCommandCopy
(and\Renew...
and\Declare...
variants) which functions almost like\LetLtxMacro
. To the end user, both should work the same way, and one shouldn’t need to worry about the definition of the command:\NewCommandCopy
should do the hard work.
\NewCommandCopy
knows about the different types of definitions from the LaTeX2ε kernel, and also from other packages, such as xparse’s command declarations like\NewDocumentCommand
, and etoolbox’s\newrobustcmd
, and it can be extended to cover further packages.(github issue 239)
Unfortunately, I don't have a sufficiently cutting-edge installation of LaTeX at hand to test out this \NewCommandCopy
(or \RenewCommandCopy
, I suppose) to provide an example of its use.