7

A TUGboat article of 15 years ago mentions an \ifnot macro by David Kastrup whose implementation is:

\def\ifnot#1{#1\else
    \expandafter\expandafter\fi
    \iffalse\iftrue\fi}

However, this macro looks a bit weird when it is applied to an \ifSomething macro, i.e., \ifnot{\ifeof\stream}. For the purpose of making TeX code a bit more readable, it might be useful to have a macro which negates the "condition" itself, without the if prefix.

Here is a minimal example, which does not work.

\documentclass{standalone}
\makeatletter
\newif\if@to@be
\begin{document}
  \@to@be@true
  \if@not\@to@be@ Not to be! \fi
  \@to@be@false
  \if@not\@to@be@ Not to be! \fi
\end{document}
Faheem Mitha
  • 7,778
Yossi Gil
  • 15,951
  • 2
    You know that e-TeX has \unless, right? – Joseph Wright Apr 06 '15 at 19:18
  • Not quite the same, \unless\if@I@miss@something but \unless\@I@miss@something{would also be a nice approximation} – Yossi Gil Apr 06 '15 at 19:21
  • 3
    the \ifnot macro is designed to be used with an \if the article you reference has an example \ifnot\iftrue The usage \if@not\@to@be@ isn't the intended usage and \@to@be@ hasn't been defined, so it's not clear what you expect it to do – David Carlisle Apr 06 '15 at 19:32
  • 1
    What about creating a new \newif (e.g., named \newifnot) so, instead of creating the macros \iftobe, \iftobetrue and \iftobefalse it would create also \ifnottobe? I think it would be easier. And may be even appending the code to \newif itself. – Manuel Apr 06 '15 at 20:12
  • How would you link the boolean and it negation? It is easy to define both in the same command, but how would the setting of one of the two correlated variables effect a setting of the other to the negated value? – Yossi Gil Apr 07 '15 at 13:15
  • @YossiGil I'm not sure of what are you asking, but I already posted an answer. It just appends code \booltrue and \boolfalse so, apart from setting \ifbool, both macros also set the correct \ifnotbool. – Manuel Apr 07 '15 at 13:19

5 Answers5

7

Possibly something like this? It preserves the OP's desired syntax \ifnot\tobe, while at the same time not demanding that \tobe be predefined. Additionally, for those who don't like using \tobe without defining it, it allows the alternate syntax \ifnot{tobe}, without any changes whatsoever.

\documentclass{article}
\makeatletter
% Following 3 lines thanks to Prof. Enrico Gregorio, from:
% http://tex.stackexchange.com/questions/42318/
% removing-a-backslash-from-a-character-sequence
\begingroup\lccode`\|=`\\
\lowercase{\endgroup\def\removebs#1{\if#1|\else#1\fi}}
\newcommand{\@macro@name}[1]{\expandafter\removebs\string#1}
%
\def\ifnot#1{%
  \edef\tmp{if\@macro@name{#1}}%
  \csname\tmp\endcsname\else
    \expandafter\expandafter\fi
    \iffalse\iftrue\fi}
\makeatother
\newif\iftobe
\begin{document}
  \tobetrue
  \ifnot\tobe Not to be! \else To be!\fi\par
  \tobefalse
  \ifnot\tobe Not to be! \else To be!\fi
\end{document}

enter image description here

6

Using e-TeX and assuming \escapechar is printable and not a space:

\documentclass{standalone}
\makeatletter
\newif\if@to@be@
\def\if@not#1{%
  \expandafter\unless\csname
    \expandafter\expandafter\expandafter i%
    \expandafter\expandafter\expandafter f%
      \expandafter\@gobble\string#1\endcsname
}
\begin{document}
  \@to@be@true
  \if@not\@to@be@ Not to be! \fi
  \@to@be@false
  \if@not\@to@be@ Not to be! \fi
\end{document}

(The restriction on \escapechar can be lifted if required: see \cs_to_str:N in expl3.)

Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
5

Having the full conditional in the argument to \ifnot is essential for the macro to work, or it cannot appear in another conditional, because TeX keeps track of \if..., \else and \fi in skipped text.

Assuming the conditional \iftobe is defined, your macro should work like

\ifnot{tobe}Not to be\else To be\fi

Now let's try

\iftrue
  \ifnot{tobe}Not to be\else To be\fi
\fi

Instead of \iftrue think to any other test, for instance \ifdim\maxdimen>0pt, that returns true. This gives no problem, because the test is removed and \ifnot is expanded, resurrecting the \iftobe which will match the first \fi.

Now consider

\iffalse
  \ifnot{tobe}Not to be\else To be\fi
\fi

The test is false, so everything up to and including the matching \else (or \fi) is skipped. Well, there is \else, so To be\fi\fi remains in the input stream. Do you see the problem? There's one unmatched \fi.

Giving to a macro a name that starts with \if doesn't make it a conditional. Only control sequences that are \let to a primitive conditional count. So TeX doesn't consider \ifnot in the skipped text to be matched by \else or \fi.

You have to use a real conditional:

\newif\iftobe

\def\NOT#1{%
  TT\fi
  \csname if#1\endcsname\else
  \expandafter\expandafter\fi
  \iffalse\iftrue\fi
}

\tobetrue

\if\NOT{tobe}Not to be\else To be\fi

\tobefalse

\if\NOT{tobe}Not to be\else To be\fi

\bye

enter image description here

As an exercise, try

\iffalse\if\NOT{tobe}Not to be\else To be\fi\fi

and see that no error is raised.

The result is the same as

\newif\iftobe

\def\ifnot#1{#1\else
  \expandafter\expandafter\fi
  \iffalse\iftrue\fi
}

\tobetrue

\ifnot{\iftobe}Not to be\else To be\fi

\tobefalse

\ifnot{\iftobe}Not to be\else To be\fi

\bye

Of course, David Kastrup's macro is much more powerful, because you can use any conditional in the argument, for instance

\ifnot{\ifdim\maxdimen>0pt}TRUE\else FALSE\fi

will print FALSE.

Of course, with e-TeX it's easier:

\unless\ifdim\maxdimen>0pt TRUE\else FALSE\fi

would do the same.


An implementation that lifts the restrictions, but is simply useless, in my opinion. The trick is to make \tobe equivalent to \iffalse, so it will count when \ifnot\tobe constructions are in the skipped text of a conditional. Of course, using \tobe in the wild is not recommended. ;-)

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn

\cs_new_protected:Npn \newifnegatable #1
 {
  \exp_args:Nc \newif { if \cs_to_str:N #1 }
  \cs_set_eq:Nc #1 { if_false: }
 }

\cs_new:Npn \ifnot #1
 {
  \use:c { if \cs_to_str:N #1 }
  \else:
  \exp_after:wN \exp_after:wN \fi:
  \if_false: \if_true: \fi:
}

\ExplSyntaxOff

\newifnegatable\tobe

\begin{document}

\tobetrue

\ifnot\tobe Not to be\else To be\fi

\iftrue\ifnot\tobe Not to be\else To be\fi\fi

\iffalse\ifnot\tobe Not to be\else To be\fi\fi

\tobefalse

\ifnot\tobe Not to be\else To be\fi

\iftrue\ifnot\tobe Not to be\else To be\fi\fi

\iffalse\ifnot\tobe Not to be\else To be\fi\fi

\end{document}

enter image description here

A perhaps more useful implementation (but \unless is much easier anyway):

\documentclass{article}
\usepackage{etoolbox}

\newcommand{\newdoubleboolean}[1]{%
  \newbool{#1}\newbool{not#1}%
  \csappto{#1true}{\setbool{not#1}{false}}%
  \csappto{#1false}{\setbool{not#1}{true}}%
  \setbool{#1}{false}%
}

\newdoubleboolean{tobe}

\begin{document}

\tobetrue

\ifnottobe Not to be\else To be\fi

\iftobe To be\else Not to be\fi

\tobefalse

\ifnottobe Not to be\else To be\fi

\iftobe To be\else Not to be\fi

\end{document}

enter image description here

egreg
  • 1,121,712
4

My solution redefines \newif macro. The new \newif\iffoo declares \iffoo and \ifnotfoo testing macros and (as usually) \footrue and \foofalse setting macros. Of course, no packages are needed. The testing macros are skipable in nested \if..\fi constructions.

\def\sdef#1{\expandafter\def\csname#1\endcsname}  
\def\newif#1{\expandafter\newifA\string#1\relax#1}
\expandafter\def\expandafter\newifA\string\if#1\relax#2{%
   \expandafter\newifB\csname ifnot#1\endcsname#2{#1}}
\def\newifB#1#2#3{% #1=\ifnotfoo, #2=\iffoo, #3=foo
   \sdef{#3true}{\let#2=\iftrue \let#1=\iffalse}% 
   \sdef{#3false}{\let#2=\iffalse \let#1=\iftrue}%
   \csname #3false\endcsname
}

%% test:
\newif\iftobe

\tobetrue
\iftobe YES\else NO\fi   
\ifnottobe NO\else YES\fi

\tobefalse
\iftobe YES\else NO\fi   
\ifnottobe NO\else YES\fi
wipet
  • 74,238
2

Here's another idea.

\documentclass{scrartcl}
\usepackage{etoolbox}
\makeatletter
\newcommand\gobblethree[3]{} % poor hackish solution expecting usual escapechar
\newcommand*\newifnot[1]
 {\newif#1%
  \csappto{\expandafter\gobblethree\string#1true}%
    {\cslet{\expandafter\newifnotaux\string#1}\iffalse}%
  \csappto{\expandafter\gobblethree\string#1false}%
    {\cslet{\expandafter\newifnotaux\string#1}\iftrue}%
  \csuse{\expandafter\gobblethree\string#1false}}
\newcommand*\newifnotaux[3]{ifnot}

\newifnot\iftobe
\begin{document}
  \tobetrue
  \ifnottobe Not to be! \fi
  \tobefalse
  \ifnottobe Not to be! \fi
\end{document}

You could even append code to \newif macro so you have all your \ifnots automatically defined.

Manuel
  • 27,118
  • How about a version of your \newifnot whose name is \new@boolean, or if you prefer the more standard convention, \newboolean. The \new@boolean@command could use the\removebs(see @Steven B. Segletes answer) macro to define a new boolean variable, allowing, e.g.,\new@boolean@tobe@, and then@tobe@true,@bobe@false,\if@tobe@and\if@not@tobe@`? – Yossi Gil Apr 07 '15 at 17:52
  • That's exactly what it does, except that \if@not@tobe@ is \ifnot@tobe@. If you want to have a @ between if and not, just edit \newifnotaux. It would be much easier if you used \newwhatever{@tobe@} rather than \newwhatever\if@tobe@ (which is exactly what the last edit of egreg does), but I used the same syntax of \newif\if@tobe@. – Manuel Apr 07 '15 at 18:23