With ε-TeX, the go-to method for testing if a `<token-list>` is empty is the following test:
\if\relax\detokenize{<token-list>}\relax
% empty
\else
% not empty
\fi
The method is fool-proof as long as the `<token-list>` can be safely `\detokenize`d, which is the case when it is grabbed as argument to some other macro which does the testing.
Now looking at the `expl3` sources I found the test to actually be (modulo `_` and `:`)
\expandafter\ifx\expandafter\qnil\detokenize{#1}\qnil
% empty
\else
% not empty
\fi
where `\qnil` are “quarks” defined with `\def\qnil{\qnil}`, which means that `\ifx\qnil<token>` will only be true if `<token>` is `\qnil`, which will be the case _only if_ `#1` is empty; otherwise `<token>` will be any other (catcode-10 or 12) token which will make the test return false.
But this condition is also true for the first test: `\if\relax<token>` will only be true if `<token>` is another control sequence, which will never be the case if there's _anything_ inside the `\detokenize`.
### Or is it?
Is there a reason for the second method being preferred over the first? Is there an edge-case in which one of them would fail?
Both methods, as far as I can tell, apply the same treatment to the input token list, and are both robust regarding weird arguments, such as `\iftrue\else\fi` (which would otherwise be a problem) because in either case the `<token-list>` is `\detokenize`d, so the argument can be virtually anything.
---
### Motivation:
I’m working on some code that will use this test and should be executed a few hundred times for each function call, so performance is important. According to my tests the first method is slightly (very, _very_ slightly) faster than the second:
```latex
\RequirePackage{l3benchmark}
\ExplSyntaxOn
\prg_new_conditional:Npnn \pho_tl_if_empty:n #1 { TF }
{
\if:w \scan_stop: \tl_to_str:n {#1} \scan_stop:
\prg_return_true:
\else:
\prg_return_false:
\fi:
}
\cs_new:Npn \pho_test:N #1
{
\benchmark_tic:
\int_step_inline:nn { 999999 }
{
#1 { } { } { } % Empty
#1 { X } { } { } % non-empty
#1 { \iftrue \else \fi } { } { } % just in case
}
\benchmark_toc:
}
\pho_test:N \pho_tl_if_empty:nTF
\pho_test:N \tl_if_empty:nTF
\stop
```
output:
```none
(l3benchmark) + TIC
(l3benchmark) + TOC: 2.17 s
(l3benchmark) + TIC
(l3benchmark) + TOC: 2.32 s
```
. . . Yes, those are 15 hundredths of a second in one million repetitions :-)
Thus, the motivation here is to know whether I can use the (in)significantly faster method without sacrificing robustness. The _real_ motivation is to know in what way this type of choice may come to bite me in the future.
# General
There are a few considerations when it comes to performance of TeX code:
1. argument grabbing costs time, don't grab arguments unnecessarily
1. `\expandafter` is slow, if you can work around it with the same amount of expansions it's faster, so instead of
```latex
\if...
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
```
we'd use (this uses an aspect of the first point, too, namely if false only the contents of the true branch will be gobbled)
```latex
\long\def\my@fi@firstoftwo\fi#1#2#3{\fi#2}
\if...
\my@fi@firstoftwo
\fi
\@secondoftwo
```
1. gobbling tokens explicitly as delimiters for arguments is faster than gobbling them as an argument which is delimited, so the above example can further be optimized:
```latex
\long\def\my@fi@firstoftwo\fi\@secondoftwo#1#2{\fi#1}
\if...
\my@fi@firstoftwo
\fi
\@secondoftwo
```
But be aware that this way code becomes less readable, less reusable, and less maintainable, so the small performance gain comes at a cost.
`\if...` can represent any if test that results in a TeX-syntax if, such as `\ifx AB`, `\iftrue`, etc.
Also `\if` tests can be slow (depending on the used test) and so is `\detokenize`, if we can get around those, we should. Another thing to consider is that `\if` tests are not robust if their arguments contains other `\if` tests, `\else` or `\fi`. To overcome this the standard test for an empty argument does `\detokenize` the argument with:
```latex
\long\def\ifemptyStandard#1%
{%
\if\relax\detokenize{#1}\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
```
This yields an unbeatable robustness, as the only possible argument that might fail this test would be an unbalanced input, which needs to be actively created, such as `\expandafter\ifemptyStandard\expandafter{\iffalse{\fi}}{true}{false}` (but who would do that anyway).
Of all the if tests built into TeX, `\ifx` is probably the fastest. So a naive test `\ifx <some-token>#1<some-token>` would be pretty fast, unfortunately this would not be robust. Cases for which it'd fail would be if `\if...`, `\else`, or `\fi` would be part of the argument or if `#1` starts with `<some-token>` (though we can make `<some-token>` pretty unlikely).
# Fast `\ifempty`
The following is a fast test, that considers some of the above mentioned aspects. We don't use any `\if...` test, but instead do the branching through TeX's argument grabbing logic:
```latex
\long\def\ifempty@true\ifempty@A\ifempty@B\@secondoftwo#1#2{#1}
\long\def\ifempty@#1\ifempty@A\ifempty@B{}
\long\def\ifempty#1%
{%
\ifempty@\ifempty@A#1\ifempty@B\ifempty@true
\ifempty@A\ifempty@B\@secondoftwo
}
```
So if `#1` is empty `\ifempty@` will gobble only the first `\ifempty@A` and `\ifempty@B` and `\ifempty@true` will be executed, gobbling the following `\ifempty@A\ifempty@B\@secondoftwo` and the false-branch. On the other hand, if `#1` is not empty everything up to `\@secondoftwo` (non-inclusive) will be gobbled and `\@secondoftwo` will execute the false-branch.
This way we get a fast testing macro (taking about 70% the time of the `\if\relax\detokenize{#1}\relax` test during my benchmarks), that's fairly robust (only input which contains `\ifempty@A\ifempty@B` will fail the test, and that should be rare).
And of course, we can use tokens which are even more unlikely than `\ifempty@A` and `\ifempty@B`, e.g., why not use a `<DEL>` characters for both but with different category codes (that should be pretty very very unlikely to ever be part of a valid argument):
```latex
\begingroup
\lccode`\&=127
\lccode`\$=127
\catcode`\&=12
\catcode`\$=11
\lowercase{\endgroup
\long\def\ifempty@true&$\@secondoftwo#1#2{#1}
\long\def\ifempty@#1&${}
\long\def\ifempty#1{\ifempty@$\ifempty@true&$\@secondoftwo}
}
```
# Fast `\ifblank`
As a small addition, we can also create a fast `\ifblank` test based on the aforementioned thoughts. The standard `\ifblank` looks something like the following:
```latex
\long\def\ifblankStandard#1%
{%
\if\relax\detokenize\expandafter{\@gobble #1.}\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
```
So essentially the same as `\ifemptyStandard` but with an `\expandafter` and a `\@gobble #1.` added. But we could do the same as for our fast `\ifempty` test with just some small additions (I'll just add this to the slightly obfuscated variant using the `<DEL>` tokens). And we don't want to use some `\expandafter`s (remember they are slow) so we use `\ifblank@` to gobble one token and insert the necessary tests of `\ifempty`.
```latex
\begingroup
\lccode`\&=127
\lccode`\$=127
\catcode`\&=12
\catcode`\$=11
\lowercase{\endgroup
\long\def\ifempty@true&$\@secondoftwo#1#2{#1}
\long\def\ifempty@#1&${}
\long\def\ifempty#1{\ifempty@$\ifempty@true&$\@secondoftwo}
\long\def\ifblank@#1{\ifempty@&}
\long\def\ifblank#1{\ifblank@#1.$\ifempty@true&$\@secondoftwo}
}
```