99

I try to understand the subtleties of TeX programming, but it is not always easy. For example, in the following code :

\def\first{abc}
\def\second{abc}
\ifx\first\second OK!\else false \fi

I understand why the output is OK!.

But I do not understand why the output is false with this code :

\if\first\second OK!\else false \fi
1

3 Answers 3

95

\if compares the following two tokens after macro expansion, because it wants to compare unexpandable tokens.

Thus \if\first\second...\fi expands \first and the input stream has now

\if abc\second...\fi

and the comparison between a and b returns false.

You can make \first and \second unexpandable by saying

\if\noexpand\first\noexpand\second...\fi

but this would return true independently of the meaning of \first and \second, because \if compares character codes and, if a token is not a character, it is considered as having character code 256 (not really, but it is convenient to think so). A control sequence will be considered as having character code 256 unless it has been defined with

\let\cs=a

(or any other character) and in this case \if a\cs would return true.

Of course, the value 256 represents any number that cannot be a character code, so it would be 0x11000 for XeLaTeX or LuaLaTeX, where character codes can be as high as 0x10FFFF.

Usage of \if is not easy, and has many subtleties. For instance one cannot use directly an active character for comparison and it must be preceded (after macro expansion) by \noexpand.

A clever example of \if is for testing whether an argument is empty:

\def\cs#1{%
  \if\relax\detokenize{#1}\relax
    The argument is empty%
  \else
    The argument #1 is non empty%
  \fi
}

It uses \detokenize which is an e-TeX feature. If the argument is empty, the comparison would be between \relax and \relax, which are equal as far as \if is concerned; otherwise, \detokenize would return a string of characters (of category code 12) and \relax is never equal to a character for \if. So with

\cs{abc}

one would get

\if\relax abc\relax
  The argument is empty%
\else
  The argument #1 is non empty%
\fi

and the true text would be

bc\relax The argument is empty\else

which would be discarded.

Similarly, with

\if a\first true\else false\fi

the expansion of \first gives

\if aabctrue\else false\fi

and, since the first two unexpandable tokens after \if are equal, the true text is

bctrue

while \else false\fi will be discarded.

5
  • @egregYour explanation, very clear, allow me to better understand the behavior of \if. One point remains to be clarified if we look at this :\if a\first \ true \else false \fi the comparison is between a and a, so the test is true. Also, why the output is bc true and not just true ?
    – Fabrice
    Commented Aug 10, 2013 at 10:33
  • @Fabrice I've added the explanation
    – egreg
    Commented Aug 10, 2013 at 19:42
  • @egregSorry, but I'm a little lock on the output. Comparing the two tokens a and a, therefore the output would not aabctrue?
    – Fabrice
    Commented Aug 11, 2013 at 22:10
  • 1
    @Fabrice No: the two tokens that are compared are swallowed during the process.
    – egreg
    Commented Aug 11, 2013 at 22:16
  • @egregThank you for your very educational explanations.
    – Fabrice
    Commented Aug 12, 2013 at 10:51
39

According to TeX By Topic, there are three kinds of \if tests concerning tokens. To understand them, you want to recall that there are basically two kinds of tokens in TeX:

  1. Character tokens, which are uniquely specified by a character code (which resembles the ASCII encoding) and a category code (which dictates TeX's interpretation of the character when it sees it).

  2. Control sequence tokens, which have a csname that is a character string (but where the characters in the string are taken without either kind of code) and also some kind of behavior that is one of:

    • Primitive, if the control sequence is predefined by TeX. In this case, two distinct primitives have different behavior.

    • Internal, if the control sequence was created by a primitive such as \countdef or \font that uses the csname to refer to some internal quantity (in the former case, a count register; in the latter case, a font). The behavior of such a control sequence depends only on which quantity it refers to.

    • Macro, if the control sequence was created by \def or its siblings. In this case its associated behavior is specified by an auxiliary token list, the expansion, as well as various auxiliary flags associated with \outer, \long, and \global.

The various \if tests compare two tokens according to their similarity in the above classification. Namely:

  • \if<tok1><tok2> compares only the character codes, under which scheme all control sequences have the same character code that is unequal to that of any actual character. Beware! It expands its arguments until it finds two unexpandable tokens to compare.

  • \ifcat<tok1><tok2> compares only the category codes, under which scheme all control sequences again have the same catcode that is unequal to that of any actual character. It also expands.

  • \ifx<tok1><tok2> uses a much finer comparison that incorporates all of the above information, distinguishing characters from each other by both kinds of code, and from control sequences, and control sequences from each other according to their behavior. It (for obvious reasons) does not expand its arguments.

These tests can seem both vacuous and confusing depending on how you think about them. For example, \if<tok1><tok2> looks really stupid if you imagine writing any specific instance of it:

\if00% True
\if01% False
\countdef\a0 \if\a0 or \if\a1% False, since \a is not a character
\countdef\a1\countdef\b1 \if\a\b% True, surprisingly (maybe)
\def\a{0}\def\b{1} \if\a\b% False, since they expand.

in which only the last case is really useful and reveals why \if needs to expand its arguments to function. Basically, it helps to compare two characters that are stored inside macros (or macro arguments, such as \if#1#2) so long as you know for sure that the two tokens you write after it both expand to one token each (or you know how to deal with the alternative).

Similarly, but oppositely, \ifx can be rather baffling, since it completely reverses the sense of a test from how \if does it, in some cases:

\ifx00% True, still
\ifx01% False, still
\countdef\a0 \ifx\a0 \ifx\a1% False, still
\countdef\a0 \countdef\b1 \ifx\a\b% Now false!
\def\a{0} \def\b{1} \ifx\a\b% Now false!

Unfortunately, perhaps, \ifx is fine-grained enough to recognize the distinction between two "aliases" for a number or character (done via \countdef, \let, and so on) but does not recognize the similarity between an alias and the thing it aliases, since the control sequence always compares false to the character underlying it.

On the plus side, it does give a nice built-in string comparison function if you store both strings in macros. Sort of an oddly sophisticated feature for a language that deals in precise measurements but lacks basic arithmetic.

1
  • 1
    @RyanYour explanation, very clear, allow me to better understand the behavior of \if and \ifx
    – Fabrice
    Commented Aug 10, 2013 at 10:43
9

The answers are already exhaustive; I just wanted a snippet that I could quickly paste in a terminal shell after typing pdflatex (only), as a quick reminder - and this seems to work for me:

\documentclass{article}
\begin{document}
%%%
%
\def\a{1}\def\b{1}  \if\a\b{\typeout{T1}}\else{\typeout{F1}}\fi
% T1
%
\def\a{1}\def\b{0}  \if\a\b{\typeout{T2}}\else{\typeout{F2}}\fi
% F2
%
\def\true{1}
%
\def\a{\true}\def\b{\true}  \if\a\b{\typeout{T3}}\else{\typeout{F3}}\fi
% T3
%
\def\a{\true}\def\b{1}      \if\a\b{\typeout{T4}}\else{\typeout{F4}}\fi
% T4
%
\def\a{\true}\def\b{0}      \if\a\b{\typeout{T5}}\else{\typeout{F5}}\fi
% F5
%
%%%
%
\def\a{1}\def\b{1}  \ifx\a\b{\typeout{T6}}\else{\typeout{F6}}\fi
% T6
%
\def\a{1}\def\b{0}  \ifx\a\b{\typeout{T7}}\else{\typeout{F7}}\fi
% F7
%
\def\a{\true}\def\b{\true}  \ifx\a\b{\typeout{T8}}\else{\typeout{F8}}\fi
% T8
%
\def\a{\true}\def\b{1}      \ifx\a\b{\typeout{T9}}\else{\typeout{F9}}\fi
% F9
%
\def\a{\true}\def\b{0}      \ifx\a\b{\typeout{T10}}\else{\typeout{F10}}\fi
% F10
%
%%%
%
\def\a{10}\def\b{10}  \if\a\b{\typeout{T11}}\else{\typeout{F11}}\fi
% F11
%
\def\a{10}\def\b{10}  \ifx\a\b{\typeout{T12}}\else{\typeout{F12}}\fi
% T12
% 

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .