Comment tester si une commande est définie ?#
1. En TeX#
Le programme TeX original, écrit par Donald Knuth, ne définit pas de commandes dédiées à cette tâche. Heureusement, ε‑TeX définit deux primitives supplémentaires :
\ifdefined
\ifcsname cmd name\endcsname
Les deux commandes utilisées dans l’exemple qui suit produisent le même effet :
\ifdefined\foo
\message{\string\foo\space is defined}%
\else
\message{no command \string\foo}%
\fi
%
\ifcsname foo\endcsname
\message{\string\foo\space is defined}%
\else
\message{no command \string\foo}%
\fi
However, after using the original LaTeX \@ifundefined{foo}...
, the conditionals will detect the command as « existing » (since it has been \let
to \relax
) ; so it is important not to mix mechanisms for detecting the state of a command.
2. En LaTeX#
Quand on programme en LaTeX, on peut directement utiliser \@ifundefined{⟨cmd name⟩}{⟨action1⟩}{⟨action2⟩}
, qui exécute ⟨action1⟩
si la commande **n’**est pas définie, et ⟨action2⟩
dans le cas contraire (⟨cmd name⟩
est le nom de la commande tout nu, sans son antislash \
).
Si vous utilisez une version de LaTeX antérieure à 2018, il faut éviter de mélanger du code qui utilise les primitives d’ε‑TeX avec du code qui utilise \@ifundefined
(voir ci-dessous pourquoi). Comme cela peut se produire d’une extension à l’autre, vous n’êtes jamais à l’abri d’une erreur…
Notez également que, même après 2018, LaTeX va toujours renvoyer « vrai si l’on utilise \@ifundefined
avec une commande définie comme un alias de \relax
.
3. Un peu d’histoire#
On trouve dans d’anciennes macros écrites en TeX le procédé suivant pour tester l’existence d’une commande ⟨commande⟩
:
\ifx\⟨commande⟩\undefined⟨code à exécuter⟩
(Ceci exécute le code si la commande **n’**existe pas, bien sûr.)
Le fonctionnement de cette commande repose sur le principe que \undefined
n’est jamais défini (donc elle est égale à une autre commande non définie). Le problème est qu’il ne s’agit que d’une convention qui peut être ignorée par un autre auteur de macros : il y a donc toujours un risque que cette macro soit définie dans une extension chargée par l’utilisateur… Utiliser \@undefined
, comme on peut le voir dans certaines macros LaTeX, ne fait que déplacer le problème.
La macro \@ifundefined
, elle, est définie dans le noyau de LaTeX, ce qui permet d’éviter ce problème. Cependant, avant 2018, elle était définie de la manière suivante :
\expandafter \ifx \csname cmd name\endcsname \relax
Elle utilisait la propriété suivante de \csname
: si la commande n’existe pas, elle est créée comme alias de \relax
. Cette approche présente deux inconvénients :
Chaque utilisation de
\@ifundefined
avec un nom de commande qui n’existe pas crée cette commande, définie comme identique à\relax
; si cette commande n’est pas redéfinie ensuite, elle est conservée inutilement en mémoire par le moteur TeX ;Si le même nom de commande est testé ensuite avec la primitive ε‑TeX
\ifdefined
(par exemple dans le code d’une autre extension), le résultat sera un faux positif, car cette primitive considère aussi comme définie la commande\relax
et ses alias.
Avant que \@ifundefined
ne soit redéfinie dans le noyau LaTeX pour être basée sur la primitive ε‑TeX \ifdefined
, David Kastrup a proposé la solution suivante :
{\expandafter}\expandafter\ifx \csname cmd name\endcsname\relax ...
La commande testée est créée et définie comme \relax
à l’intérieur du groupe dans lequel est inclus le premier \expandafter
: elle n’est donc pas conservée en mémoire après l’exécution de \@ifundefined
.
Sources