Por que o idioma \expandafter\@firstoftwo?

Por que o idioma \expandafter\@firstoftwo?

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 \expandafterestá aí? Eu teria pensado que eles pegariam o primeiro par do {true}{false}par seguinte?

Responder1

Os \expandafters são para lidar com o seguinte \elseou \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 \ifeqabsorve o \stuffe \nonsenseassim temos após a primeira expansão:

\ifx\stuff\nonsense\relax
  \expandafter\@firstofone
\else
  \expandafter\@secondoftwo
\fi
{true}{false}

Suponhamos que \stuffsim \nonsense(ou seja, que a condicional seja verdadeira). Em seguida, \ifxcomeça a expandir tudo em seu caminho "verdadeiro", que é definido como tudo até o próximo \elseou \fi(aninhamento de módulo). O ponto chave é que ele começa a se expandirprimeiroe não olha para frente para encontrar o \elseor \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 \@firstoftwoe \elseexpandi-lo. "Expandir" \elsesignifica removê-lo e tudo até a correspondência \fido fluxo. Então ficamos com:

\@firstoftwo{true}{false}

E então isso é expandido para o simples true.

Sem o \expandafters 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 \elsee \@secondoftwo. Em seguida, ele deixa o primeiro no fluxo, então obtemos

\else
\fi
{true}{false}

O \elsecorresponde à condicional, então o TeX absorve isso e tudo até \fisair {true}{false}no fluxo. O que não é o que queríamos.

Em resumo, o \expandaftersobjetivo é 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, \ifhmodenão precisa de token, \ifxprecisa 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 \elsetoken, levando em consideração condicionais aninhadas que podem aparecer <true>sem expandir nada. Portanto, um \elsepertencente a um \if...\else\fiinterior aninhado <true>será ignorado. A expansão também está vazia neste caso e todos os tokens até \elsedesaparecerão. Caso nenhuma correspondência \elseseja encontrada, o TeX irá parar de olhar para a correspondência \fique deveria estar em algum lugar. Então, nos dois casos, teríamos

<false>\fi

ou simplesmente nada se não \elsehouvesse 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 \elseconsiste em retirar tudo até a correspondência \fie não deixar nada no fluxo de entrada.As condicionais aninhadas serão levadas em consideração como antes.

  • A expansão de \fiestá 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 \elsee é aqui que a diversão começa.

A expansão de \expandafterconsiste 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 Tdo 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 \expandafterfaz seu trabalho de expansão \fie desaparecimento. Assim obtemos

\@secondoftwo{T}{F}

isso finalmente vai embora F.

Nota importante

No caso de \@ifundefined{foo}{T}{F}conseguirmos chegar Tou Fnãoexecutandoum comando: apenas a expansão macro foi usada. Isso torna \@ifundefineduma 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 \@firstoftwoseriam \elsee \@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 \expandafters 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 \elseou \fisã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 \@firstoftwoe \@secondoftwoseriam, respectivamente, \else\@secondoftwoee \fio 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.

informação relacionada