¿Está bien que un comando absorba más argumentos de los que se le pasan?

¿Está bien que un comando absorba más argumentos de los que se le pasan?

¿Está bien que una secuencia de control \fooincluya otra secuencia de control \slurpque absorba más argumentos de los que \foorealmente 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,articledefine \sectioncomo

\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 \@startsectiontoma 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 \sectionsolo 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 \makeboxdeben 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 xparsesituació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.

información relacionada