
¿Está bien que una secuencia de control \foo
incluya otra secuencia de control \slurp
que absorba más argumentos de los que \foo
realmente le pasan?
Por ejemplo, ¿está bien hacer esto?
\documentclass{article}
\newcommand\foo [1]{#1 \slurp}
\newcommand\slurp[3]{#1 #2 #3}
\begin{document}
\foo{a}{b}{c}{d}
\end{document}
¿En lugar de esto?
\documentclass{article}
\newcommand\foo [4]{#1 \slurp{#2}{#3}{#4}}
\newcommand\slurp[3]{#1 #2 #3}
\begin{document}
\foo{a}{b}{c}{d}
\end{document}
Respuesta1
¿Esta bien? ¡Sí, de hecho! De hecho, existen numerosos usos para este tipo de definiciones de macros. En particular, las definiciones fundamentales para las variantes destacadas de los comandos. Por ejemplo,article
define \section
como
\newcommand\section{\@startsection {section}{1}{\z@}%
{-3.5ex \@plus -1ex \@minus -.2ex}%
{2.3ex \@plus.2ex}%
{\normalfont\Large\bfseries}}
Mira como se necesitaceroargumentos, aunque normalmente lo especificamos/usamos como \section[<toc>]{<title>}
?! Esto se debe a que \@startsection
toma 6 argumentos y luego realiza una prueba para ver si el usuario agregó una estrella o no. Delatex.ltx
:
\def\@startsection#1#2#3#4#5#6{%
\if@noskipsec \leavevmode \fi
\par
\@tempskipa #4\relax
\@afterindenttrue
\ifdim \@tempskipa <\z@
\@tempskipa -\@tempskipa \@afterindentfalse
\fi
\if@nobreak
\everypar{}%
\else
\addpenalty\@secpenalty\addvspace\@tempskipa
\fi
\@ifstar
{\@ssect{#3}{#4}{#5}{#6}}%
{\@dblarg{\@sect{#1}{#2}{#3}{#4}{#5}{#6}}}}
Como tal, los argumentos que normalmente especificamos \section
solo son devorados por una macro dos etapas después.
Otro buen ejemplo de por qué esto es una buena práctica tiene que ver con los cambios en los códigos de categoría. Una vez que los argumentos se consumen para su uso, sus códigos de categoría no se pueden cambiar. Por lo tanto, generalmente se usa una macro auxiliar para modificar los códigos de categoría.antesdevorando cualquier argumento.
Hay muchos otros ejemplos en elNúcleo de látex, desde macros de fuentes básicas hasta cómo manejar el ToC, e incluso definir un nuevo comando a través de \newcommand
:
\def\newcommand{\@star@or@long\new@command}
Nuevamente, otra macro que no acepta ningún argumento, pero realiza alguna operación antes de pasar el testigo a otra macro. En general, este principio se utiliza bien en todo el kernel y los paquetes.
Respuesta2
Como se explica en la respuesta de Werner, esta es una práctica común. Todas las macros que tienen una variante * se definen de esta manera:
\newcommand{\foo}{\@ifstar{\@sfoo}{\@foo}}
\newcommand{\@sfoo}[1]{Foo with * applied to #1}
\newcommand{\@sfoo}[1]{Foo without * applied to #1}
o variantes de los mismos. De manera similar, las macros que tienen más de un argumento opcional, como \makebox
deben tomar una ruta larga para decidir si hay uno o dos argumentos opcionales:
\newcommand{\bar}{\@ifnextchar[{\@bar@i}{\@bar}}
\def\@bar@i[#1]{\@ifnextchar[{\@bar@ii{#1}}{\@bar@iii{#1}}
\def\@bar@ii#1[#2]#3{Bar has two optional arguments, #1 and #2, and #3}
\def\@bar@iii#1#2{Bar has one optional argument, #1, and #2}
\def\@bar#1{Bar has no optional argument and #1}
La xparse
situación es bastante diferente: dado que las variantes * y los argumentos opcionales se pueden especificar de una manera bastante general, se prefiere cargartodoargumentos reales:
\usepackage{xparse}
\NewDocumentCommand{\foo}{sm}{%
\IfBooleanTF{#1}
{Foo with * applied to #2}
{Foo without * applied to #2}%
}
\NewDocumentCommand{\bar}{oom}{%
\IfNoValueTF{#1}
{Bar has no optional argument and #3}
{\IfNoValueTF{#2}
{Bar has one optional argument, #1, and #3}
{Bar has two optional arguments, #1 and #2, and #3}%
}%
}%
}
Este es "el futuro" con LaTeX3.