10

For practical reasons I want to substitute the somewhat verbose

\ifcsname some_variable\endcsname
\fi

by a combination of

\def\customif#1{\ifcsname #1\endcsname}

\customif{some_variable}
\fi

The first works as expected even if some_variable is undefined, while the \customif approach yields a somewhat unexpected result.

I know that I must be missing something, but I can not figure out why the following code fails to compile.

\documentclass{article}

\def\customif#1{\ifcsname #1\endcsname}

\begin{document}
    \expandafter\def\csname some_variable\endcsname{}

    \ifcsname some_variable\endcsname
        This works.
    \fi

    \customif{some_variable}
        So does this.
    \fi

    \ifcsname other_variable\endcsname
        \ifcsname other_variable\endcsname
            This compiles and does not print (as expected).
        \fi
    \fi

    \customif{other_variable}
        This also compiles and does not print.
    \fi

    \customif{other_variable}
        \customif{other_variable}
            This fails.
        \fi
    \fi

    \customif{other_variable}
    \else
        \customif{other_variable}
        \else
            On the other hand, this compiles and prints.
        \fi
    \fi
\end{document}

Compiling with pdflatex I get

! Extra \fi.
l.30     \fi
11

In your example

\customif{other_variable}
    \customif{other_variable}
        This fails.
    \fi
\fi

the inner \customif is not expanded, so the \ifcsname in its replacement text is not seen. So the first \fi is paired with the \ifcsname resulting from the expansion of the outer \customif and the next one is dangling alone.

More detailed: the code, after expansion of \customif{other_variable} becomes

\ifcsname other_variable\endcsname
    \customif{other_variable}
        This fails.
    \fi
\fi

The test returns false, so everything up to the first \fi is skipped over.

You can do it in a different way:

\def\custom#1{TT\fi\ifcsname #1\endcsname}

and then call it as

\if\custom{some_variable}
  <true text>
\else
  <false text>
\fi

which will work also nested because of the \if. (Don't forget to protect the line endings when necessary.)


If you're worried about a dangling initial \fi (that will however disappear immediately upon expansion) you can do

\def\custom#1{T\expandafter X\expandafter X\fi\ifcsname #1\endcsname}

A different approach is to use “braced branches”:

\makeatletter
\newcommand*\vardefTF[1]{%
  \ifcsname #1\endcsname
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\makeatother

to be called like

\vardefTF{some_var}{True}{False}

This has the disadvantage that the true and false branches are read in as arguments, but has less problems when nested.

With expl3 this is predefined, so

\usepackage{expl3}

\ExplSyntaxOn
\cs_set_eq:NN \vardefTF \cs_if_exist:cTF
\ExplSyntaxOff

would provide \vardefTF behaving like before.


A different idea, suggested by Bruno Le Floch:

\def\custom#1{%
  T\ifcsname#1\endcsname
  \expandafter T\else\expandafter F\fi
}

If the code

\if\custom{some_variable}<True>\else<False>\fi

is inside a skipped conditional branch, the \if will be balanced by \else (optional as usual) and \fi. On the other hand, when \if is not skipped and expanded, when \ifcsname returns true, \if will find TT, otherwise it will find TF, choosing accordingly the following branches.

This has the advantage that one can prefix \if with \unless.

(The old TT\fi trick was born before \unless became available.)

  • Great answer, thank you! What exactly does TT\fi stand for? – jcmonteiro May 8 '17 at 22:42
  • 1
    @jcmonteiro When \if\custom{...} is expanded, \if TT\fi disappears and only \ifcsname ...\endcsname remains; when unexpanded, the \if serves to balance \else and \fi. – egreg May 8 '17 at 22:46
  • 1
    I’d also suggest switching to LaTeX-style conditional, e.g., \newcommand*{\ifcustom}[1]{\ifcsname #1\endcsname \expandafter\@firstoftwo \else \expandafter\@secondoftwo \fi}. Why don’t you add this to your answer? – GuM May 8 '17 at 22:55
  • There is something wrong in your middle example. Maybe \def\custom#1{\fi \ifcsname#1\endcsname} is sufficient (without any \expandafter and without any TXX). – wipet May 9 '17 at 12:16
  • @wipet Remember about the “frozen \relax”; with just \fi instead of TT\fi two \relax tokens would be inserted, which are unwanted in a full expansion context. – egreg May 9 '17 at 12:27
7

I am using different approach than TT for such situations. I define \is... macro which realizes the test and it has one more parameter than is really needed. This (ignored) parameter is \iftrue when the macro is used. Our example looks like:

\def\isdefined#1#2{\ifcsname #1\endcsname}

and usage:

\isdefined{some_text}\iftrue
   <true text>
\else
   <false text>
\fi

Note that you need not to put % at the end of the first line.

  • Interesting approach, but, IMHO, less readabe than that presented in @egreg’s answer. – GuM May 9 '17 at 8:17
  • @GustavoMezzetti But \cs_set_eq:NN \vardefTF \cs_if_exist:cTF (in egreg's answer) is generally unreadable. – wipet May 9 '17 at 13:03
  • 1
    @GustavoMezzetti and wipet: readability is so much about what you're used to, what you've worked with, and personal preference :) IMHO for example texts in Chinese are generally unreadable but I bet Chinese people will beg to differ… ;) – clemens May 9 '17 at 14:36
  • \if\isdefined{some_text}...\fi has the slight advantage of expanding in one step, provided we define \def\isdefined#1{T\expandafter\expandafter\ifcsname #1\endcsname T\else F\fi}. – Bruno Le Floch May 9 '17 at 15:35
6

Another possibility is to use a LaTeX-style conditional. For example, in the code below we define a command \CustomIfDef, with should be used as follows

\CustomIfDef{<cs-name>}{<if-defined-code>}{<if-undefined-code>}

and can be nested without problems. Note, however, that in case of nesting it is less efficient than @egreg approach, because the code for the various cases needs to be copied between the levels as an argument.

% My standard header for TeX.SX answers:
\documentclass[a4paper]{article} % To avoid confusion, let us explicitly 
                                 % declare the paper format.

\usepackage[T1]{fontenc}         % Not always necessary, but recommended.
% End of standard header.  What follows pertains to the problem at hand.

\makeatletter

\newcommand*\CustomIfDef[1]{%
    \ifcsname #1\endcsname
        \expandafter\@firstoftwo
    \else
        \expandafter\@secondoftwo
    \fi
}
\newcommand*\CustomDefEmpty[1]{\@namedef{#1}{}}

\makeatother

\nofiles



\begin{document}

\CustomDefEmpty{Foo_bar_123.$}

\CustomIfDef{Pluto!}{
    \typeout{==> Outer branch true.}
}{
    \typeout{==> Outer branch false.}
    \CustomIfDef{Foo_bar_123.$}{
        \typeout{----> Mid-level branch true.}
        \CustomIfDef{relax}{
            \typeout{......> Inner branch: \relax is defined.}
        }{
            \typeout{......> Inner branch: \relax is NOT defined.}
        }
    }{
        \typeout{----> Mid-level branch false.}
    }
}

\end{document}
  • Plus one for the \typeout command which I didn't know about! – jcmonteiro May 9 '17 at 13:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.