¿Cómo cambiar el valor de un argumento (por ejemplo, n.° 1) de modo que cualquier referencia a él (el argumento, n.° 1) se expanda al nuevo valor?

¿Cómo cambiar el valor de un argumento (por ejemplo, n.° 1) de modo que cualquier referencia a él (el argumento, n.° 1) se expanda al nuevo valor?

Me preguntaba cómo cambiar #1, según su valor, a otra cosa de modo que cualquiera #1que siga al cambio contenga el valor modificado, no el valor original (pasado a la macro desde fuera).

\def\myT#1%
  {%
    \ifnum#1=0
      % #1=1
    \fi
    #1
  }
\myT{0} % 1
\myT{1} % 1
\myT[2} % 2

EDITAR (solución alternativa; preferiría cambiar #1el valor de directamente, como #1=1, que expandir un \myTargumento anidado de 1, como se muestra a continuación):

\def\myT#1%
  {%
    \ifnum#1=0
      \myT{1}%
    \else
      #1%
    \fi
  }
\myT{0} % 1
\myT{1} % 1
\myT{2} % 2

Respuesta1

No puede "asignar" valores a #1otra cosa que no sea utilizar el argumento apropiado.

El tipo de tarea que presentas se realiza más cómodamente con dos macros; Si bien puede llamar \myT{1}cuando el argumento original es 0, esto es ineficiente ya que realiza nuevamente una verificación que ya se realizó.

\def\myT#1{%
  \ifnum#1=0
    \myTaux{1}%
  \else
    \myTaux{#1}%
  \fi
}
\def\myTaux#1{-#1-\par}

\myT{0} % 1
\myT{1} % 1
\myT{2} % 2

\bye

No hay nada malo en dividir el funcionamiento de una función deseada en dos o más macros; al contrario, en general es una buena práctica.

Respuesta2

Recuerde que tex es un lenguaje de procesamiento de macros, no un lenguaje funcional. #1no es un identificador que hace referencia a una variable pasada por referencia a una función, es simplemente un marcador de posición donde están insertados los tokens proporcionados.

Entonces, si tiene sentido asignar algo #1depende de lo #1que sea.

dado

\def\myT#1%
  {%
    \ifnum#1=0
       #1=1 %HERE
    \fi
    #1 %THERE
  }
\myT{0} % 1

La línea marcada AQUÍ es 0=1para que no sea una asignación, simplemente escribe 0=1. Sin embargo, si usó la misma definición pero la llamó como

\newcount\zzz
\zzz=0
\myT{\zzz}

Entonces la línea marcada AQUÍ sería \zzz=1y sería una tarea, pero la línea marcada ALLÍ sería \zzzasí que no se escribiría, 1sería necesario \the#1y se evaluaría como\the\zzz

Respuesta3

El proceso que podría estar más cerca de lo que usted describe como "cambiar el valor de un argumento" es hacer que la macro en cuestión se llame a sí misma nuevamente con el argumento en cuestión cambiado.

Esta técnica de programación, donde, dependiendo de alguna condición, lo último que hace una rutina es terminar y volver a llamarse a sí misma, se llama recursividad de cola.

En terminología (La)TeX: una macro (La)TeX es recursiva de cola cuando, dependiendo de alguna condición, los últimos tokens entregados como resultado de su expansión forman otra llamada.

Cuando necesite \if.. \else.. \fi-switches para verificar las condiciones para la terminación de la recursividad de cola, asegúrese de que los tokens que forman las ramas \else- y - coincidentes \fise procesen/descarten del búfer de entrada antes de que se vuelva a llamar a la macro en cuestión. De lo contrario, la macro en cuestión no es recursiva en la cola, ya que los tokens que forman estas ramas permanecen y se acumulan en el flujo de tokens/en el búfer de entrada hasta que se procesan en un momento en el que la recursividad ya ha terminado, lo que lleva a tomas innecesarias. Peaje para el nido semántico, y también conduce a una acumulación innecesaria de tokens en el búfer de entrada.

Puedes, por ejemplo, hacer algo como esto:

\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\def\myT#1{%
  \ifnum#1=0 \expandafter\firstoftwo\else\expandafter\secondoftwo\fi
   {\myT{1}}{#1}%
}%
\myT{0}% 1

\myT{1}% 1

\myT{2}% 2
%...
\bye

En caso de que necesite realizar cambios más sofisticados, puede ser útil intercambiar argumentos después de activar los pasos de expansión necesarios. Supongamos una macro que procesa dos argumentos y donde, en el caso de que el primer argumento tenga el valor 0, ese argumento debe reemplazarse por uno con el valor 1 y el segundo argumento debe reemplazarse por uno cuyo valor aumente en 1:

% this example requires eTeX-extensions (\numexpr):
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\passfirsttosecond#1#2{#2{#1}}%
\def\myT#1#2{%
  \ifnum#1=0 \expandafter\firstoftwo\else\expandafter\secondoftwo\fi
  {%
    \expandafter\passfirsttosecond\expandafter{\number\numexpr#2+1\relax}{\myT{1}}%
  }{%
    Value of 1st arg: #1.\hfil\break
    Value of 2nd arg: #2.%
  }%
}%
\myT{0}{5}% Value of 1st arg: 1.\hfil\break Value of 2nd arg: 6.

\myT{1}{6}% Value of 1st arg: 1.\hfil\break Value of 2nd arg: 6.

\myT{2}{7}% Value of 1st arg: 2.\hfil\break Value of 2nd arg: 7.
%...
\bye

En caso de necesitar cambiar más de dos argumentos, puede anidar llamadas \passfirsttoseconddentro \passfirsttoseconddel segundo argumento. Si hace esto, obtendrá un patrón en el que los cambios del último pero k-ésimo argumento macro que debe cambiarse se realizarán justo antes de llevar a cabo la primera pero k-ésima \passfirsttoseconddirectiva:

Supongamos una macro que procesa nueve argumentos y donde, en el caso de que el primer argumento tenga el valor 0, ese argumento debe reemplazarse por uno con el valor 1 y cada uno de los siguientes argumentos debe reemplazarse por uno en el que el valor aumente en 1:

% this example requires eTeX-extensions (\numexpr):
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\passfirsttosecond#1#2{#2{#1}}%
\def\myT#1#2#3#4#5#6#7#8#9{%
  \ifnum#1=0 \expandafter\firstoftwo\else\expandafter\secondoftwo\fi
  {%
    \expandafter\passfirsttosecond\expandafter{\number\numexpr#9+1\relax}{%
      \expandafter\passfirsttosecond\expandafter{\number\numexpr#8+1\relax}{%
        \expandafter\passfirsttosecond\expandafter{\number\numexpr#7+1\relax}{%
          \expandafter\passfirsttosecond\expandafter{\number\numexpr#6+1\relax}{%
            \expandafter\passfirsttosecond\expandafter{\number\numexpr#5+1\relax}{%
              \expandafter\passfirsttosecond\expandafter{\number\numexpr#4+1\relax}{%
                \expandafter\passfirsttosecond\expandafter{\number\numexpr#3+1\relax}{%
                  \expandafter\passfirsttosecond\expandafter{\number\numexpr#2+1\relax}{%
                    \expandafter\passfirsttosecond\expandafter{\number\numexpr#1+1\relax}{%
                      \myT
                    }%
                  }%
                }%
              }%
            }%
          }%
        }%
      }%
    }%
  }{%
    Value of 1st arg: #1.\hfil\break
    Value of 2nd arg: #2.\hfil\break
    Value of 3rd arg: #3.\hfil\break
    Value of 4th arg: #4.\hfil\break
    Value of 5th arg: #5.\hfil\break
    Value of 6th arg: #6.\hfil\break
    Value of 7th arg: #7.\hfil\break
    Value of 8th arg: #8.\hfil\break
    Value of 9th arg: #9.%
 }%
}%
\myT{0}{4}{9}{14}{19}{24}{29}{34}{39}%
%    Value of 1st arg: 1.\hfil\break
%    Value of 2nd arg: 5.\hfil\break
%    Value of 3rd arg: 10.\hfil\break
%    Value of 4th arg: 15.\hfil\break
%    Value of 5th arg: 20.\hfil\break
%    Value of 6th arg: 25.\hfil\break
%    Value of 7th arg: 30.\hfil\break
%    Value of 8th arg: 35.\hfil\break
%    Value of 9th arg: 40.%

\myT{1}{5}{10}{15}{20}{25}{30}{35}{40}%
%    Value of 1st arg: 1.\hfil\break
%    Value of 2nd arg: 5.\hfil\break
%    Value of 3rd arg: 10.\hfil\break
%    Value of 4th arg: 15.\hfil\break
%    Value of 5th arg: 20.\hfil\break
%    Value of 6th arg: 25.\hfil\break
%    Value of 7th arg: 30.\hfil\break
%    Value of 8th arg: 35.\hfil\break
%    Value of 9th arg: 40.%

\myT{2}{1}{2}{3}{4}{5}{6}{7}{8}%
%    Value of 1st arg: 2.\hfil\break
%    Value of 2nd arg: 1.\hfil\break
%    Value of 3rd arg: 2.\hfil\break
%    Value of 4th arg: 3.\hfil\break
%    Value of 5th arg: 4.\hfil\break
%    Value of 6th arg: 5.\hfil\break
%    Value of 7th arg: 6.\hfil\break
%    Value of 8th arg: 7.\hfil\break
%    Value of 9th arg: 8.%
%...
\bye

Como puede ver en los ejemplos anteriores, la programación (La)TeX y especialmente el concepto de "expansión" de (La)TeX no se trata tanto de asignar valores a variables, sino más bien de hacer girar los llamados tokens mientras usted puede tomar tokens para cosas/elementos que se colocan en el flujo de tokens/flujo de entrada uno detrás del otro.

La siguiente analogía podría resultar útil a la hora de entrar en contacto con el concepto de macroexpansión en (La)TeX:

Cuando (La)TeX procesa un archivo de entrada .tex, es decir, algún fragmento del código fuente de (La)TeX, en la primera etapa toma ese archivo de entrada .tex como un conjunto de instrucciones para insertar tokens en el flujo de tokens. (Estos tokens pueden ser tokens de caracteres de cualquier código de categoría, tokens de palabras de control y tokens de símbolos de control). En etapas posteriores, estos tokens se procesarán. Durante la etapa de expansión, los tokens serán eliminados/reemplazados por otros tokens. Durante la etapa de expansión, también se puede cambiar el orden en que aparecen los tokens en el flujo de tokens.

En caso de que esté interesado en detalles sobre las reglas que (La)TeX sigue para insertar tokens en el flujo de tokens al leer el archivo de entrada .tex, la discusión"Sangría del código fuente"podría ser de su interés.

En caso de que esté interesado en saber qué tipos de tokens hay en (La)TeX, la discusión"¿Cuál es la diferencia entre 'macro' y 'comando'?"podría ser de su interés.

En caso de que esté interesado en cómo se manejan los macroargumentos en (La)TeX, la discusión"¿Cómo busca TeX argumentos delimitados?"podría ser de su interés.

En caso de que esté interesado en los trucos de expansión, la discusión"¿Cómo puedo saber el número de archivos expandibles cuando los agrego a una macro csname?"podría ser de su interés.


Como ya señaló siracusa, no se puede asignar otra secuencia de token a un argumento macro cuando (La)TeX ha recopilado ese argumento del flujo de token.
Pero puedes hacer que (La)TeX examine ese argumento para "decidir" qué tokens se entregarán al final. Esto se puede hacer directamente o llamando a otra macro donde los argumentos pasados ​​dependen del resultado del examen.

Ejemplo de acercamiento directo:

\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\def\myT#1{%
  \ifnum#1=0 %<- The space before the percent terminates \ifnum's second number, i.e., the number 0. It gets discarded silently.
    \expandafter\firstoftwo%<-\expandafter removes the tokens that form the else-branch and the \fi
  \else
    \expandafter\secondoftwo%<-\expandafter removes the \fi
  \fi
  {1}{#1}%
}%
\myT{0} % 1
\myT{1} % 1
\myT[2} % 2

Generalmente \romannumeralse utiliza para obtener tokens que forman la notación romana de un número entero en letras minúsculas. Cuando se debe \romannumeralbuscar un número, (La)TeX seguirá expandiendo tokens expandibles hasta encontrar un número o mostrar un mensaje de error. En caso de que el número en cuestión no sea positivo, (La)TeX lo tragará silenciosamente sin entregar ningún token. Por lo tanto, puede (ab?)usar \romannumeralpara desencadenar una gran cantidad de trabajo de expansión y trabajo de examen de argumentos siempre que se asegure de que al final (La)TeX encuentre un número que no sea positivo.

Otro ejemplo de enfoque directo, invocar \romannumeralcomo desencadenante de la expansión para garantizar que el resultado de, \myTen cualquier caso, se entregue después de dos pasos de expansión/después de "golpear" el lugar donde se encuentra \myTdos \expandafterveces:

\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\def\myT#1{%
   \romannunmeral0%<-While searching the number for \romannumneral, (La)TeX finds the digit-token 0 and keeps
                  %   triggering expansion while searching in the token-stream for
                  %   more digit-tokens or something that terminates the number.
   \ifnum#1=0 %<- The space before the percent terminates \ifnum's second number, i.e., the number 0. It gets discarded silently.
     \expandafter\firstoftwo%<-\expandafter removes the tokens that form the else-branch and the \fi
   \else
     \expandafter\secondoftwo%<-\expandafter removes the \fi
   \fi
   { 1}{ #1}% <- The space that precedes `1`/`#1` will be right
            %    behind the "0" from "\romannumeral0".
            %    Therefore it stops (La)TeX's seeking for digits for
            %    \rommanumeral which means that after doing
            %    expansion-work/argument-examining-work (La)TeX
            %    does only find the non-positive number "0" and 
            %    thus the result/effect of `\romannumeral` is:
            %    "Triggering doing a lot of expansion- and argument-
            %     examining-work while silently discarding the
            %     digit-token 0 which forms a non-positive number
            %     and the space behind it and not delivering
            %     anything in roman-notation at all."
}%
\myT{0} % 1
\myT{1} % 1
\myT[2} % 2

Ejemplo de llamada a otra macro:

\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\newcommand\myinnerT[1]{%
  Here \firstoftwo{\LaTeX}{} can do to the argument, #1, whatever shall be done.%
}%
\def\myT#1{%
  % Here the argument is examined for deciding how to call \myinnerT:
  \ifnum#1=0 %
    \expandafter\firstoftwo
  \else
    \expandafter\secondoftwo
  \fi
  {\myinnerT{1}}{\myinnerT{#1}}%
}%
\myT{0} % 1
\myT{1} % 1
\myT[2} % 2

Tenga en cuenta que con los ejemplos anteriores no se implementa la comprobación de si el argumento en alguna etapa de la expansión produce un número. (Estrictamente hablando, tal verificación es imposible en caso de que el argumento pueda consistir en secuencias de tokens arbitrarias, ya que en este caso los tokens que forman el argumento podrían formar ellos mismos la implementación (La)TeX de cualquier algoritmo y, por lo tanto, dicha verificación (debajo otras cosas) requieren resolver elproblema de detención.)

información relacionada