Vejo muitas macros que são testes condicionais seguidos por um {true}{false}
par definido para que resultem em:
\expandafter\@firstoftwo
ou
\expandafter\@secondoftwo
Por que isso \expandafter
está aí? Eu teria pensado que eles pegariam o primeiro par do {true}{false}
par seguinte?
Responder1
Os \expandafter
s são para lidar com o seguinte \else
ou \fi
. Como na pergunta à qual Ryan está vinculado, o código completo é algo como:
\def\ifeq#1#2{%
\ifx#1#2\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
Vamos rastrear o que acontece. Colocamos \ifeq\stuff\nonsense{true}{false}
em nosso documento. O \ifeq
absorve o \stuff
e \nonsense
assim temos após a primeira expansão:
\ifx\stuff\nonsense\relax
\expandafter\@firstofone
\else
\expandafter\@secondoftwo
\fi
{true}{false}
Suponhamos que \stuff
sim \nonsense
(ou seja, que a condicional seja verdadeira). Em seguida, \ifx
começa a expandir tudo em seu caminho "verdadeiro", que é definido como tudo até o próximo \else
ou \fi
(aninhamento de módulo). O ponto chave é que ele começa a se expandirprimeiroe não olha para frente para encontrar o \else
or \fi
. O TeX imagina que saberá quando chegar a hora. Então nós temos:
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{true}{false}
O TeX agora expande isso \expandafter
. Isto tem o efeito de alcançar o \@firstoftwo
e \else
expandi-lo. "Expandir" \else
significa removê-lo e tudo até a correspondência \fi
do fluxo. Então ficamos com:
\@firstoftwo{true}{false}
E então isso é expandido para o simples true
.
Sem o \expandafter
s lá, chegamos a:
\@firstoftwo
\else
\@secondoftwo
\fi
{true}{false}
O TeX ainda está expandindo o ramo verdadeiro, então expande \@firstoftwo
. Isso absorve dois tokens/grupos de chaves do fluxo. Acontece que estes são \else
e \@secondoftwo
. Em seguida, ele deixa o primeiro no fluxo, então obtemos
\else
\fi
{true}{false}
O \else
corresponde à condicional, então o TeX absorve isso e tudo até \fi
sair {true}{false}
no fluxo. O que não é o que queríamos.
Em resumo, o \expandafters
objetivo é tirar o processamento condicional do caminho antes doresultadoda condicional é expandida, garantindo assim que o resultado da condicional veja os próximos bits no fluxo e não os bits restantes da condicional inacabada.
Responder2
Eu atacaria isso de um ponto de vista diferente. Condicionais primitivos no TeX testam uma condição, que pode absorver tokens do fluxo de entrada ou não até que a verdade ou falsidade da condição possa ser estabelecida. Então, vamos denotar pela
<IF>
condicional primitiva junto com a lista (possivelmente vazia) de tokens que devem ser absorvidos. Por exemplo, \ifhmode
não precisa de token, \ifx
precisa de dois. Em alguns casos ( \if
, \ifcat
, \ifnum
, \ifdim
) TeX realiza expansões para encontrar o tipo de token necessário para o teste; em outros ( \ifx
, \ifmmode
, \ifhmode
, \ifvmode
\ifinner
, \iftrue
, \iffalse
) nenhuma expansão é realizada. Assim <IF>
denotará os tokens condicionais e necessáriosdepoisa expansão ocorreu e a condição pode ser testada.
A construção típica a que você está se referindo é
<IF>
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
onde já temos
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
De forma mais geral, temos
<IF><true>\else<false>\fi
ou
\IF<true>\fi
onde ambos <true>
e <false>
podem estar vazios.
A condição é verdadeira
O TeX simplesmente será removido <IF>
do fluxo de entrada, deixando
<true>\else<false>\fi
ou
<true>\fi
A condição é falsa
O TeX procura o próximo \else
token, levando em consideração condicionais aninhadas que podem aparecer <true>
sem expandir nada. Portanto, um \else
pertencente a um \if...\else\fi
interior aninhado <true>
será ignorado. A expansão também está vazia neste caso e todos os tokens até \else
desaparecerão. Caso nenhuma correspondência \else
seja encontrada, o TeX irá parar de olhar para a correspondência \fi
que deveria estar em algum lugar. Então, nos dois casos, teríamos
<false>\fi
ou simplesmente nada se não \else
houvesse nenhuma filial lá.
Isso é provado pela seguinte entrada do 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
Executar o TeX nele produzirá a seguinte transcrição:
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}
?
O que acontece depois
A expansão de
\else
consiste em retirar tudo até a correspondência\fi
e não deixar nada no fluxo de entrada.As condicionais aninhadas serão levadas em consideração como antes.A expansão de
\fi
está vazia.
O papel de\expandafter
Agora temos as bases sobre as quais podemos cair \expandafter
.
Vejamos um uso típico:
\def\@ifundefined#1{%
\expandafter\ifx\csname#1\endcsname\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi}
O que queremos é poder dizer
\@ifundefined{foo}{T}{F}
Assim, a macro construída com o argumento como nome é comparada \relax
(esta é realmente a parte desinteressante) e então o ramo verdadeiro ou falso é seguido.
Após a remoção do <IF>
permanecemos com
\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi{T}{F}
e agora o TeX expande devidamente o primeiro token. Isso desencadeia a expansão \else
e é aqui que a diversão começa.
A expansão de \expandafter
consiste em expandir (se possível) o token após o próximo e desaparecer.Assim \else
é expandido de acordo com a regra acima e ficamos com
\@firstoftwo{T}{F}
isso resulta na saída T
do fluxo de entrada.
Suponha agora que a condição seja falsa. Depois <IF>
é retirado junto com tudo até \else
, deixando
\expandafter\@secondoftwo\fi{T}{F}
Agora \expandafter
faz seu trabalho de expansão \fi
e desaparecimento. Assim obtemos
\@secondoftwo{T}{F}
isso finalmente vai embora F
.
Nota importante
No caso de \@ifundefined{foo}{T}{F}
conseguirmos chegar T
ou F
nãoexecutandoum comando: apenas a expansão macro foi usada. Isso torna \@ifundefined
uma macro definida de forma semelhante utilizável dentro de \edef
:
\edef\test{\@ifundefined{foo}{T}{F}}
será equivalente a
\def\test{T}
in case \foo
é definido (e não equivalente a \relax
, como de costume no LaTeX) ou para
\def\test{F}
if \foo
é indefinido (ou equivalente a \relax
).
O que aconteceria sem \expandafter
? Com um TeX condicional verdadeiro seria confrontado com
\@firstoftwo\else\@secondoftwo\fi{T}{F}
e os dois argumentos para \@firstoftwo
seriam \else
e \@secondoftwo
, o que não faria nada de útil, não é?
Da mesma forma, para uma condição falsa, obteríamos
\@secondoftwo\fi{T}{F}
e novamente as coisas dariam errado.
Responder3
O contexto completo para esse código está em macros condicionais, como
\def\IfZero#1{%
\ifnum0=#1\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
Os \expandafter
s são necessários se você observar o código de maneira menos formatada:
\def\IfZero#1{\ifnum0=#1\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}
(desculpas pela longa fila) em que nenhuma pista é dada sobre a estrutura dos blocos condicionais do TeX. Como você pode ver, o token "depois" \@firstoftwo
é \else
, e o token "depois" \@secondoftwo
é \fi
, que são respectivamente expandidos por \expandafter
. O propósito dessa bobagem é que o TeX não lê toda a condicional quando expande o \ifnum
; ele apenas avança para o bloco verdadeiro ou falso correto e continua a partir daí. Os \else
ou \fi
são deixados no fluxo de entrada! Uma vez expandidos, o TeX verifica até o final da condicional e a próxima coisa no fluxo de entrada é o seguinte \IfZero
.
Sem qualquer um deles \expandafter
, os "dois" consumidos por \@firstoftwo
e \@secondoftwo
seriam, respectivamente, \else\@secondoftwo
ee \fi
o argumento macro seguinte, em vez do que realmente se pretende, ou seja, os dois argumentos macro seguintes que são tomados como o "segundo" e o "terceiro" "argumentos" de \IfZero
.