El equipo de LaTeX empezó a trabajar en un analizador general de argumentos, xparse, a finales de los años noventa; su pieza central, \NewDocumentCommand, no dejó de ser un paquete experimental para entrar en el núcleo de LaTeX hasta la versión del 1 de octubre de 2020. Ese aprendizaje de dos décadas tiene una razón. \newcommand solo sabe contar cuántos argumentos toma una orden, mientras que \NewDocumentCommand describe de qué tipo es cada uno, mediante una cadena llamada especificación de argumentos (arg-spec). Pasar de contar a describir da a cambio variantes con estrella, varios argumentos opcionales independientes y argumentos encerrados entre delimitadores elegidos a voluntad: sintaxis que \newcommand no puede expresar en absoluto. Esta página recorre lo que promete cada letra especificadora, cuándo conviene \IfNoValueTF en lugar de \IfBooleanTF y cuándo es mejor no recurrir a nada de esto.
Lo que \newcommand no puede expresar: un solo argumento opcional y en primera posición
\newcommand construye una sola forma: como mucho un argumento opcional entre corchetes, y solo en primera posición, seguido de los obligatorios. Cualquier cosa más rica exigía, según deja constancia LaTeX News 32, bajar hasta la primitiva \def de TeX y a la programación de macros de bajo nivel. De ahí que las fuentes de los paquetes antiguos estén llenas de maquinaria escrita a mano para espiar el siguiente token: \@ifstar para detectar una estrella, \@ifnextchar para detectar un carácter cualquiera. Como esos nombres llevan @, hay que envolverlos en \makeatletter; se rompen con facilidad ante espacios y anidamientos, y a nadie le resulta grato leerlos.
\NewDocumentCommand sustituye todo ese espionaje por una gramática declarativa. Se le pasa una cadena de letras en lugar de un número; el analizador lee la entrada y entrega siempre al cuerpo argumentos normalizados como #1, #2, etc. Así queda separada la interfaz que ven los usuarios del código que la implementa. Dentro del núcleo esta maquinaria vive en un módulo llamado ltcmd, y desde la versión del 1 de octubre de 2020 \usepackage{xparse} es innecesario. El paquete xparse sigue en CTAN, pero el README del conjunto l3packages que lo distribuye se titula ya «Deprecated» y explica que el material se conserva para dar soporte a archivos antiguos. La excepción son los tipos de argumento obsoletos g/G, l y u: usar uno de ellos produce Invalid argument type "g" in command "\zzz" (requires xparse). El código nuevo no tiene prácticamente ningún motivo para quererlos.
Cómo se escribe \NewDocumentCommand y en qué se diferencian New, Renew, Provide y Declare
La forma básica toma tres argumentos: \NewDocumentCommand{\cmd}{⟨arg-spec⟩}{⟨cuerpo⟩} — el nombre de la orden, la especificación de argumentos y el cuerpo, donde los argumentos llegan como #1, #2, etc. Cambiar el verbo inicial cambia la actitud ante un nombre ya ocupado. Si \NewDocumentCommand apunta a un nombre existente, la compilación se detiene con LaTeX cmd Error: Command "\section" already defined. Es una red de seguridad, no una molestia: para sustituir una definición existente se usa \RenewDocumentCommand, y para definir solo si no hay nada, \ProvideDocumentCommand.
| Declaración | Comportamiento ante un nombre ya definido |
|---|---|
\NewDocumentCommand | Se detiene con error si está ocupado; es la opción por defecto |
\RenewDocumentCommand | Da error si el nombre no existe; sirve para rehacer una orden ya definida |
\ProvideDocumentCommand | Define solo si no hay nada; así un paquete cubre un hueco de compatibilidad |
\DeclareDocumentCommand | Sobrescribe sin condiciones; la documentación oficial pide usarlo con moderación |
Las órdenes creadas con cualquiera de estas cuatro declaraciones traen una propiedad que nadie tuvo que pedir: son robustas desde el principio. Basta aplicar \meaning a una de ellas para ver \protected macro:->…, señal de que el mecanismo \protected de ε-TeX trabaja a nivel del motor. Colocada en un argumento móvil —un título de sección, un pie de figura— no necesita ningún \protect delante. Por qué una definición hecha con \newcommand se rompe justo en esos lugares, y qué hacía al respecto \DeclareRobustCommand, corresponde a la página «Definir macros».
Los especificadores de argumentos: qué significan m, o, O{}, s, t, r, d, e, v y b
Una especificación de argumentos es una cadena en la que una letra describe un argumento, y los especificadores se reparten en dos familias. La obligatoria la forman m, r, R, v, b; la opcional, o, O, d, D, s, t, e, E. Una sola regla recorre todo el conjunto: un tipo en mayúscula permite fijar un valor por defecto, mientras que su equivalente en minúscula devuelve el marcador especial -NoValue-. Léase o frente a O{...}, d frente a D, e frente a E, r frente a R: el patrón se cumple siempre. Internamente, señala la documentación, o, d y O no son más que atajos hacia un argumento de tipo D construido a medida.
| Especificador | Significado | Cómo llega al cuerpo |
|---|---|---|
m | Argumento obligatorio: grupo entre llaves o token único | Un #1 corriente, sin las llaves exteriores |
r | r⟨d1⟩⟨d2⟩ — obligatorio, entre delimitadores elegidos | -NoValue- tras un error si falta el delimitador inicial |
R | R⟨d1⟩⟨d2⟩{defecto} — como r, con valor de recuperación propio | El valor por defecto escrito, si falta |
v | Argumento verbatim leído como \verb; el delimitador no puede ser %, \, #, {, } ni un espacio | Los caracteres literales; no puede ir dentro del argumento de otra orden |
b | El cuerpo de un entorno; solo en \NewDocumentEnvironment y en última posición | Todo lo que hay entre \begin y \end |
o | El argumento opcional [...] estándar | -NoValue- si no se proporcionó |
O | O{defecto} — o con valor por defecto | El valor por defecto si falta; siempre hay valor |
d | d⟨d1⟩⟨d2⟩ — opcional, delimitado por caracteres a elección | -NoValue- si no se proporcionó |
D | D⟨d1⟩⟨d2⟩{defecto} — d con valor por defecto | El valor por defecto si falta |
s | Detecta una estrella * inicial | \BooleanTrue o \BooleanFalse |
t | t⟨car⟩ — comprueba un carácter dado; es la generalización de s | \BooleanTrue o \BooleanFalse |
e | e{⟨tokens⟩} — un conjunto de adornos como ^ y _; los tokens han de ser distintos | Un argumento por token, -NoValue- para cada ausente |
E | E{⟨tokens⟩}{⟨defectos⟩} — e con valores por defecto | Si la lista es más corta, el resto recae en -NoValue- |
Los tipos delimitados (r, R, d, D) traen restricciones que conviene conocer. Primero, los caracteres de agrupación { y } de TeX no pueden hacer de delimitadores: al escribir r{} se recibe un LaTeX cmd Error: Argument delimiter "" invalid in command "\zzz". Lo habitual es elegir caracteres que se emparejan de forma natural: [], (), <>, "". Segundo, cuando el delimitador es un token de carácter, el analizador memoriza el código de categoría que tenía en el momento de la definición. Si más tarde se convierte < en letra, ese mismo < deja de reconocerse como delimitador. Una secuencia de control usada como delimitador (algo como \x) queda a salvo, porque se identifica por su nombre y no por su significado actual.
% t<char> tests for one character; r()...() is a required delimited argument
\NewDocumentCommand{\pt}{t+ r()}{%
\IfBooleanTF{#1}{\mathbf{(#2)}}{(#2)}%
}
$\pt(1,2)$ % -> (1,2)
$\pt+(3,4)$ % -> (3,4) in bold
% e{^} picks up an optional ^ embellishment wherever it appears
\NewDocumentCommand{\deriv}{e{^} m m}{%
\frac{\mathrm{d}\IfNoValueF{#1}{^{#1}}#3}{\mathrm{d}#2\IfNoValueF{#1}{^{#1}}}%
}
$\deriv{x}{f}$ % -> df/dx
$\deriv^{2}{x}{f}$ % -> d^2 f / dx^2Los modificadores +, !, > y = que preceden a un especificador
+ convierte un argumento en largo, es decir, capaz de tragarse una línea en blanco y por tanto un cambio de párrafo. Aquí está la primera mina para quien llega desde \newcommand: el valor por defecto está invertido. \newcommand hace largos todos los argumentos, y se escribe la forma con estrella \newcommand* cuando se los quiere cortos. \NewDocumentCommand hace lo contrario: los argumentos son cortos por defecto y se pone + delante de cada uno que deba ser largo. De modo que una orden recién portada a la que se le entrega un texto con una línea en blanco responde con ! Paragraph ended before \remark was complete. El ajuste argumento por argumento es justamente la ganancia: una sola declaración puede decir que el título breve ocupa un párrafo mientras que el cuerpo admite varios.
Los otros tres se explican rápido. ! prohíbe un espacio justo antes de un argumento opcional, y solo puede aplicarse a un argumento opcional final: puesto al principio produce Invalid argument prefix "!" in command "\remark". Es lo que hace falta cuando los corchetes de \foo{x} [x] deben leerse como texto corriente. > introduce un procesador de argumentos: al escribir >{\SplitArgument{2}{;}} m, a;b;c se parte en tres argumentos antes de que el cuerpo lo vea. El núcleo trae \SplitArgument, \SplitList, \TrimSpaces, \ProcessList y \ReverseBoolean. = es un modificador más reciente que obliga a interpretar un argumento opcional como pares clave-valor; existe para que órdenes con larga tradición de texto libre —\caption y las de seccionado— puedan ganar una interfaz clave-valor sin romper la sintaxis antigua.
% + makes ONE argument long; ! on a trailing optional argument forbids a space
\NewDocumentCommand{\remark}{+m !o}{\par\textbf{Note.} #1 (#2)\par}
\remark{first paragraph
second paragraph}[tag]
\remark{x} [these brackets stay ordinary text]
% > runs a processor before the body sees the argument
\NewDocumentCommand{\triple}{>{\SplitArgument{2}{;}} m}{\tripleaux#1}
\NewDocumentCommand{\tripleaux}{m m m}{(#1/#2/#3)}
\triple{a;b;c} % -> (a/b/c)\IfNoValueTF frente a \IfBooleanTF, y la diferencia entre o y O{}
Hay dos familias de pruebas, y el especificador decide cuál corresponde. Para los tipos que devuelven -NoValue- —o, d, e— se usa \IfNoValueTF{#1}{⟨si falta⟩}{⟨si está⟩}; para los que devuelven un booleano —s, t— se usa \IfBooleanTF{#1}{⟨verdadero⟩}{⟨falso⟩}. También existe el \IfValueTF de lógica invertida, y ambas familias traen formas de una sola rama: \IfNoValueT, \IfNoValueF, \IfValueT, \IfValueF, \IfBooleanT, \IfBooleanF. Lo interesante es por qué hubo que inventar \IfNoValueTF: un argumento opcional omitido es realmente distinto de uno entregado vacío. El mecanismo de valores por defecto de \newcommand no puede expresar esa diferencia en absoluto: el valor por defecto simplemente aparece, y el hecho de que el usuario no escribiera nada nunca llega al cuerpo.
-NoValue- es un buen sistema antifalsificación: está construido para no coincidir con el texto literal -NoValue-, de modo que \IfNoValueTF{-NoValue-} es lógicamente falso. La comparación de cadenas no sirve como sustituto: hay que comprobarlo siempre con \IfNoValueTF. La trampa clásica consiste en confundir o con O{}. Con o, un argumento omitido es realmente -NoValue- y \IfNoValueTF ramifica bien; con O{} siempre hay un valor —vacío si se omite—, así que \IfNoValueTF cae siempre en la rama falsa. Y si uno olvida comprobarlo del todo, la señal suele ser la cadena -NoValue- impresa tal cual en el PDF compuesto.
¿Con qué se comprueba entonces si un argumento O{} está vacío? Justo ahí cambió la recomendación oficial en junio de 2022. El núcleo proporciona \IfBlankTF (junto con \IfBlankT y \IfBlankF), que da verdadero cuando el argumento está realmente vacío o solo contiene espacios. Para diseños con dos argumentos opcionales seguidos, la documentación aconseja ahora O{} combinado con \IfBlankTF en lugar de comprobar por separado la vacuidad y -NoValue-. No hace falta recurrir a \tl_if_blank:nTF de expl3 ni a \ifblank de etoolbox. Un matiz: \IfBlankTF cuenta una orden como \space como contenido real, pues imprime un espacio pero como token tiene sustancia.
% s = optional star, o = optional [..], m = mandatory
\NewDocumentCommand{\heading}{s o m}{%
\IfBooleanTF{#1}
{\section*{#3}}% starred: unnumbered
{\IfNoValueTF{#2}
{\section{#3}}% no short title given
{\section[#2]{#3}}}% short title for the ToC
}
\heading{A Long Introduction} % numbered section
\heading[Intro]{A Long Introduction} % short title in the table of contents
\heading*{Preface} % unnumbered
% with O{} the value is always there, so test for blankness instead
\NewDocumentCommand{\tagged}{O{} m}{\IfBlankTF{#1}{#2}{[#1] #2}}Ese ejemplo de encabezado esconde otra propiedad que \newcommand no puede imitar: los argumentos opcionales creados con \NewDocumentCommand se anidan sin riesgo. En el propio ejemplo de la documentación, \foo[\baz[stuff]]{more stuff} se analiza correctamente aunque un argumento opcional contenga una orden que a su vez toma uno. Los corchetes de una definición con \newcommand agarran ingenuamente todo hasta el siguiente ], de modo que la misma entrada queda truncada en el corchete interior. En cuanto se quiere poner una orden con argumento opcional dentro de otro argumento opcional, ya hay motivo suficiente para pasarse a \NewDocumentCommand.
\NewDocumentEnvironment y el tipo b: recibir el cuerpo del entorno como argumento
Los entornos disponen de la misma maquinaria mediante \NewDocumentEnvironment{⟨env⟩}{⟨arg-spec⟩}{⟨código inicial⟩}{⟨código final⟩} (junto con \Renew…, \Provide… y \Declare…). Los argumentos se dan justo después de \begin{⟨env⟩} y son visibles tanto para el código inicial como para el final. Y aquí aparece un especificador sin equivalente del lado de las órdenes: b, el cuerpo mismo del entorno. Si b cierra la especificación de argumentos, todo lo que hay entre \begin y \end llega como un único argumento, listo para transformarse, componerse dos veces o descartarse bajo condición.
Con b vienen tres convenciones. Primero, al cuerpo se le recortan por defecto los espacios de ambos extremos, así que no hay que preocuparse por los blancos al final de línea; se escribe !b para desactivar ese recorte. Segundo, se usa +b si el cuerpo puede abarcar varios párrafos. Tercero —y esto se olvida con facilidad—, con b el código final resulta superfluo, pero el cuarto argumento vacío hay que escribirlo igualmente; sin él, \NewDocumentEnvironment cuenta mal sus argumentos. Los entornos que usan b pueden anidarse unos dentro de otros. Para las bases de \newenvironment y los entornos corrientes que no necesitan b, véase la página «Entornos personalizados».
% b grabs the whole body; + allows several paragraphs; the empty 4th
% argument is still required even though there is no end code left to run
\NewDocumentEnvironment{shout}{O{\bfseries} +b}{#1#2}{}
\begin{shout}[\itshape]
Loud and clear.
\end{shout}Cuándo hace falta \NewExpandableDocumentCommand: al inicio de una celda de tabla y dentro de \edef
Que la declaración estándar produzca órdenes robustas —que no se expanden a la ligera— es una ventaja en casi todas partes y un estorbo en unos pocos sitios. El ejemplo más práctico es el comienzo de una celda de tabla: la maquinaria de tabular exige que cualquier orden que envuelva a \multicolumn sea expandible, mientras que una orden creada con \NewDocumentCommand bloquea la expansión a propósito mediante una función del motor. Lo mismo ocurre cuando hay que fijar el contenido dentro de \edef o \write. Para eso existe \NewExpandableDocumentCommand, con sus hermanas \Renew…, \Provide… y \Declare…. La documentación oficial es tajante: úsese solo cuando sea necesario, porque trae restricciones consigo.
- Si hay argumentos, el último debe ser
m,roR, uno de los tipos obligatorios. - El tipo verbatim
vno está disponible, ni tampoco los procesadores de argumentos>ni el modificador clave-valor=. - No distingue
\foo[de\foo{[}: en ambos casos el corchete se lee como inicio de un argumento opcional, de modo que la detección de opcionales es menos fiable que en la versión estándar. - En cambio, los tipos booleanos
sytsí funcionan.\IfBooleanTFes expandible por sí mismo, así que la rama se resuelve como se espera incluso dentro de un\edef.
% a command wrapping \multicolumn must be expandable to work in a cell
\NewExpandableDocumentCommand{\wide}{m}{\multicolumn{3}{c}{#1}}
\begin{tabular}{lcr}
a & b & c \\
\wide{spans three columns} \\
\end{tabular}¿Cuál usar: \newcommand o \NewDocumentCommand?
Para una abreviatura sin argumentos, o con uno o dos obligatorios, \newcommand basta de sobra. Reescribir \newcommand{\R}{\mathbb{R}} con \NewDocumentCommand no aporta más que caracteres adicionales. \newcommand ni ha envejecido ni ha sido desaconsejado: sigue siendo una herramienta LaTeX de pleno derecho junto a la interfaz más reciente. Las señales para cambiar son, en cambio, muy nítidas: cuando se quiere una variante con estrella, cuando hace falta un segundo argumento opcional, cuando la sintaxis de entrada debe ser algo distinto de [...], y cuando una orden con argumento opcional tiene que ir dentro de otro argumento opcional. Si se da alguno de esos casos, una línea de especificación de argumentos resulta más corta y mucho más legible que la fontanería de \@ifstar escrita a mano.
Una última pauta apunta en sentido contrario. \NewDocumentCommand es una herramienta para diseñar sintaxis de entrada, no un lenguaje para escribir lo que hace el cuerpo. En cuanto uno se pone a partir cadenas, apilar condicionales o iterar una vez recogidos los argumentos, ha entrado en el terreno de expl3, la capa de programación de LaTeX3, que es, de hecho, aquello en lo que está escrito ltcmd. A la inversa, al diseñar órdenes de cara al usuario dentro de un paquete o una clase, \NewDocumentCommand es la primera opción, porque la especificación de argumentos se lee al mismo tiempo como el pliego de la interfaz.