[Tex/LaTex] Use of \relax after \ifnum … \fi construction

conditionalsmacrosplain-textex-core

I'm reading Malcolm Clark's A Plain TeX Primer. On page 111 there's an exercise to define a macro that prints the current time. I've reproduced Clark's answer below:

\newcount\minleft
\newcount\timehour
\def\thetime{\timehour=\time
  \divide\timehour by60 %gives 24 hour part of clock
  \minleft=\timehour
  \multiply\minleft by-60 %
  \advance\minleft by\time    %minutes after hour
  \ifnum\time>720\advance\timehour by-12\fi\relax
  \number\timehour:\ifnum\minleft<10 %
                   0\fi\relax\number\minleft
  \ifnum\time>720~p.m.\else~a.m.\fi}

My problem is that I don't know what \relax is doing in lines 8 and 10 (after each \fi). When I use this macro, it prints the time as it's supposed to:

8:51 p.m.

If I delete the \relax in line 8, I get the time but with the hour missing:

:51 p.m.

If I delete the \relax in line 10 (and restore the \relax in line 8), the macro works again like it did the first time:

8:51 p.m.

I naively thought that the \fi that ends the \ifnum block ends the processing of whatever's going on in the \ifnum ... \fi, and that it wouldn't matter whether \relax is there or not, in either place. Clearly that's not the case, but I can't figure out what's happening in line 8, and also why the \relax doesn't seem to be necessary in line 10.

In a different context the author does explain that \relax is a "do nothing" command that (in this other context) prevents TeX from treating the expression before the \relax and after the \relax parts of the same expression. I get that. But concerning \relax after an \fi, he says:

"The \relax is probably not required, but it's always nice to have one in: perhaps an example of 'safe TeX'."

This might be good advice, but it is not enlightening.

I found the following questions about \relax on tex.se, but I don't think they answer my question (or at least I'm not smart enough to figure it out):

What is the difference between \relax and {}?

What does \relax do?

Difference between \relax and % for ending a line

Normal \relax vs. frozen \relax

So, what is \relax actually doing in this macro?

Best Answer

I don't know this primer, but I am a little leery of any book that claims to be instructional but cheerfully advises you to do something "just in case". There is always a reason; in this case, the reason is one of the characteristic quirks of TeX.

The problem is not so much the \ifnum as the \advance. The amount by which you are to advance is scanned for by TeX in an oppositely-greedy fashion: it goes as far as possible into the input stream, expanding all the way, until it encounters a primitive quantity that cannot be part of a number. Then it takes what it found as the operand. Here is how the expression

\ifnum\time>720\advance\timehour by-12\fi%\relax
  \number\timehour:

is parsed. First, it checks \time against 720, and if so begins to execute \advance. This reads -12 and then continues reading. Now, the thing you were missing here is that \fi is not syntactical: that is, it is still in the input stream at this point, not having been removed when TeX processed the \ifnum. And it's expandable. Expanding it does terminate the conditional, but there is no kind of lexical scope for the branches, so expansion continues past \fi and reaches \number\timehour, which is an expandable quantity that becomes whatever number \timehour currently is. Then comes the :, and this is non-expandable and non-numerical, so scanning stops.

The result is that, if \time > 720, we advance \timehour by the value -12<value of \timehour before advancing>. That whole number is absorbed, though after advancing it is basically thrown away since the only piece of code actually using \timehour has been removed. On the contrary, if the \relax were not commented, since it is not expandable, it would kill the expansion immediately after \fi and \number\timehour would not be thrown away; since it is a no-op, it has no other effect. This explains why, in its absence, the hour disappears from the output and only the colon remains.

I don't think this is great style. It detaches the \relax from the thing that it's protecting. Probably better is to write

\ifnum\time>720\advance\timehour by-12\relax\fi
  \number\timehour:

so the entire phrase \advance \timehour by -12\relax is a single operation. You can also just put a space after the -12, but be careful that it's not thrown away carelessly; TeX's blase attitude towards spaces lends itself to lots of programming glitches (like any language feature that makes it easy to write code in a colloquial rather than a strict formal style).

Related Question