The first thing to remember is that \if
will do recursive macro expansion until finding two unexpandable tokens.
The second important fact is that TeX will ignore explicit space tokens when looking for an undelimited macro argument.
Let's examine what happens with the calls
\if\blank{a}<true>\else<false>\fi
\if\blank{}<true>\else<false>\fi
\if\blank{ }<true>\else<false>\fi
\if\blank{a}<true>\else<false>\fi
TeX will expand \blank
, so #1
will be a
, to get
\if\bl@nk a@@..\bl@nk•<true>\else<false>\fi
(there is no space after the first \bl@nk
, I used it only to see where the token ends). Now the macro \bl@nk
will be expanded. It looks for an undelimited argument, then for an argument delimited by @
, then for an undelimited argument and finally for an argument delimited by \bl@nk
. In the present case,
#1 <- a
#2 <- (empty)
#3 <- @
#4 <- ..
and will substitute the replacement text, so we obtain
\if @..<true>\else<false>\fi
Since @
and .
haven't the same character code, the test will return false and so
<false>\fi
will remain in the input stream.
It would be similar if instead of \if\blank{a}
we had \if\blank{abc}
, because in this case we'd get
#1 <- a
#2 <- bc
#3 <- @
#4 <- ..
\if\blank{}<true>\else<false>\fi
At the first step we get
\if\bl@nk @@..\bl@nk<true>\else<false>\fi
(again, the space after the first \bl@nk
does not exist). Now \bl@nk
looks for its arguments:
#1 <- @
#2 <- (empty)
#3 <- .
#4 <- .
and so the input stream will have
\if..<true>\else<false>\fi
and now the test returns true.
\if\blank{ }<true>\else<false>\fi
The same as in the previous case: we get
\if\bl@nk @@..\bl@nk<true>\else<false>\fi
but now the space after \bl@nk
is what's obtained from the argument to \blank
. Not a big deal! The second rule mentioned above applies and that space is ignored, leaving the things exactly as in the second case.
Note
As David Carlisle points out, this is not particularly robust, because if this is used in a context where @
has category code 11, the test might return true even if the argument is not blank, precisely if we call \if\blank{@@}
. Indeed, in this case the first step yields
\if\bl@nk @@@@..\bl@nk<true>\else<false>\fi
and we have
#1 <- @
#2 <- (empty)
#3 <- @
#4 <- @..
so we'd end up with
\if @@..<true>\else<false>\fi
and the test would return true. The two periods would appear in the input stream.
A safer test would be with very strange characters, say Q
with category code 3 that's normally not found.