Veo muchas macros que son pruebas condicionales seguidas de un {true}{false}
par definido para que den como resultado:
\expandafter\@firstoftwo
o
\expandafter\@secondoftwo
¿Por qué están estas \expandafter
ahí? ¿Habría pensado que simplemente atraparían el primer aparato ortopédico del siguiente {true}{false}
par?
Respuesta1
Los \expandafter
s son para tratar lo siguiente \else
o \fi
. Como en la pregunta a la que se vincula Ryan, el código completo es algo como:
\def\ifeq#1#2{%
\ifx#1#2\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
Rastreemos lo que sucede. Ponemos \ifeq\stuff\nonsense{true}{false}
en nuestro documento. El \ifeq
absorbe el \stuff
y \nonsense
así tenemos después de la primera expansión:
\ifx\stuff\nonsense\relax
\expandafter\@firstofone
\else
\expandafter\@secondoftwo
\fi
{true}{false}
Supongamos que así \stuff
es \nonsense
(es decir, que el condicional es verdadero). Luego \ifx
comienza a expandir todo en su camino "verdadero", que se define como todo hasta el siguiente \else
o \fi
(anidamiento de módulo). El punto clave es que comienza a expandirse.primeroy no mira hacia adelante para encontrar el \else
o \fi
. TeX cree que lo sabrá cuando llegue el momento. Entonces tenemos:
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{true}{false}
TeX ahora amplía eso \expandafter
. Esto tiene el efecto de extenderse hacia \@firstoftwo
el \else
y expandirlo. "Expandir" \else
significa eliminarlo y todo lo que coincida \fi
de la transmisión. Entonces nos quedamos con:
\@firstoftwo{true}{false}
Y luego esto se expande a lo simple true
.
Sin la \expandafter
s allí, llegamos a:
\@firstoftwo
\else
\@secondoftwo
\fi
{true}{false}
TeX todavía está expandiendo la verdadera rama, por lo que se expande \@firstoftwo
. Esto absorbe dos fichas/grupos reforzados de la secuencia. Estos resultan ser \else
y \@secondoftwo
. Luego deja el primero en la secuencia para que obtengamos
\else
\fi
{true}{false}
Coincide \else
con el condicional, por lo que TeX absorbe esto y todo hasta \fi
dejarlo {true}{false}
en la secuencia. Que no es lo que queríamos.
En resumen, \expandafters
deben eliminar el procesamiento condicional antes de queresultadodel condicional se expande, asegurando así que el resultado del condicional vea los siguientes bits en la secuencia y no los bits que quedan del condicional inacabado.
Respuesta2
Yo atacaría esto desde un punto de vista diferente. Los condicionales primitivos en TeX prueban una condición, que puede absorber tokens del flujo de entrada o no hasta que se pueda establecer la verdad o falsedad de la condición. Entonces, denotemos por
<IF>
el condicional primitivo junto con la lista (posiblemente vacía) de tokens que deben ser absorbidos. Por ejemplo, \ifhmode
no necesita ficha, \ifx
necesita dos. En algunos casos ( \if
, \ifcat
, \ifnum
, \ifdim
) TeX realiza expansiones para encontrar el tipo de tokens requerido para la prueba; en otros ( \ifx
, \ifmmode
, \ifhmode
, \ifvmode
\ifinner
, \iftrue
, \iffalse
) no se realiza ninguna expansión. Así <IF>
se indicarán los tokens condicionales y requeridos.despuésSe ha producido una expansión y se puede probar la condición.
La construcción típica a la que te refieres es
<IF>
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
donde ya tenemos
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
De manera más general, tenemos
<IF><true>\else<false>\fi
o
\IF<true>\fi
donde ambos <true>
y <false>
pueden estar vacíos.
La condición es verdadera.
TeX simplemente se eliminará <IF>
del flujo de entrada, dejando
<true>\else<false>\fi
o
<true>\fi
La condición es falsa.
TeX busca el siguiente \else
token, teniendo en cuenta los condicionales anidados que pueden aparecer <true>
sin expandir nada. Por lo tanto , se omitirá una \else
pertenencia a un \if...\else\fi
interior anidado. <true>
La expansión también está vacía en este caso y todas las fichas hasta \else
desaparecerán. En caso de que no \else
se encuentre ninguna coincidencia, TeX dejará de buscar la coincidencia \fi
que debería estar en alguna parte. Entonces, en los dos casos, obtendríamos
<false>\fi
o simplemente nada si no \else
había ninguna sucursal.
Esto se demuestra con la siguiente entrada TeX:
\def\showx{\show\x}
\def\showif{\afterassignment\showx
\expandafter\def\expandafter\x\expandafter}
\showif{\ifvmode<true>\else<false>\fi}
\showif{\ifvmode<true>\fi}
\showif{\ifhmode<true>\else<false>\fi}
\showif{\ifhmode<true>\fi}
\bye
Al ejecutar TeX en él se producirá la siguiente transcripción:
This is TeX, Version 3.1415926 (TeX Live 2012)
(./plkfi.tex
> \x=macro:
-><true>\else <false>\fi .
\showx ->\show \x
l.5 \showif{\ifvmode<true>\else<false>\fi}
?
> \x=macro:
-><true>\fi .
\showx ->\show \x
l.6 \showif{\ifvmode<true>\fi}
?
> \x=macro:
-><false>\fi .
\showx ->\show \x
l.7 \showif{\ifhmode<true>\else<false>\fi}
?
> \x=macro:
->.
\showx ->\show \x
l.8 \showif{\ifhmode<true>\fi}
?
¿Qué pasa después?
La expansión de
\else
consiste en eliminar todo hasta la coincidencia\fi
y no dejar nada en el flujo de entrada.Los condicionales anidados se tendrán en cuenta como antes.La expansión de
\fi
está vacía.
El rol de\expandafter
Ahora tenemos las bases sobre las cuales podemos caer \expandafter
.
Veamos un uso típico:
\def\@ifundefined#1{%
\expandafter\ifx\csname#1\endcsname\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi}
Lo que queremos es poder decir
\@ifundefined{foo}{T}{F}
Entonces, la macro construida con el argumento como nombre se compara con \relax
(esta es realmente la parte que no es interesante) y luego se sigue la rama verdadera o falsa.
Después de la eliminación del <IF>
nos quedamos con
\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi{T}{F}
y ahora TeX amplía debidamente el primer token. Esto desencadena la expansión de \else
y aquí es donde comienza la diversión.
La expansión de \expandafter
consiste en expandir (si es posible) el token después del siguiente y desaparecer.Así \else
se expande según la regla anterior y nos queda
\@firstoftwo{T}{F}
eso resulta en salir T
en el flujo de entrada.
Supongamos ahora que la condición es falsa. Luego <IF>
se retira junto con todo hasta \else
dejar
\expandafter\@secondoftwo\fi{T}{F}
Ahora \expandafter
hace su trabajo de expandirse \fi
y desaparecer. Así obtenemos
\@secondoftwo{T}{F}
que finalmente se va F
.
Nota IMPORTANTE
En el caso de que \@ifundefined{foo}{T}{F}
podamos llegar a T
o F
sin nuncaejecutandoun comando: solo se ha utilizado la expansión macro. Esto hace que \@ifundefined
una macro definida de manera similar sea utilizable dentro \edef
:
\edef\test{\@ifundefined{foo}{T}{F}}
será equivalente a
\def\test{T}
en caso \foo
está definido (y no es equivalente a \relax
, como es habitual en LaTeX) o a
\def\test{F}
si \foo
no está definido (o es equivalente a \relax
).
¿Qué pasaría sin \expandafter
? Con un verdadero condicional, TeX se enfrentaría a
\@firstoftwo\else\@secondoftwo\fi{T}{F}
y los dos argumentos \@firstoftwo
serían \else
y \@secondoftwo
, que no harían nada útil, ¿verdad?
De manera similar, para una condición falsa obtendríamos
\@secondoftwo\fi{T}{F}
y nuevamente las cosas saldrían mal.
Respuesta3
El contexto completo de dicho código se encuentra en macros condicionales como
\def\IfZero#1{%
\ifnum0=#1\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
Los \expandafter
s son necesarios si observa el código en una forma menos formateada:
\def\IfZero#1{\ifnum0=#1\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}
(disculpas por la larga línea) en el que no se da ninguna pista sobre la estructura de los bloques condicionales de TeX. Como puede ver, el token "después" \@firstoftwo
es \else
y el siguiente \@secondoftwo
es \fi
, que se expanden respectivamente en \expandafter
. El propósito de esta tontería es que TeX no lea el condicional completo cuando expande el \ifnum
; simplemente escanea hacia adelante hasta el bloque verdadero o falso correcto y continúa desde allí. ¡Los \else
o \fi
se dejan en el flujo de entrada! Una vez que se expanden, TeX escanea hasta el final del condicional y lo siguiente en el flujo de entrada es lo siguiente \IfZero
.
Sin ninguno de los dos \expandafter
, los "dos" consumidos por \@firstoftwo
y \@secondoftwo
serían, respectivamente, \else\@secondoftwo
y \fi
el siguiente macro argumento, en lugar de lo que realmente se pretende, es decir, los dos siguientes macro argumentos que se toman como el "segundo" y el "tercer" "argumento". de \IfZero
.