4

In etoolbox.sty, three commands \ifdefmacro, \ifdefparam and \ifdefprotected are firstly defined by \newcommand\cmd{}, then by \long\edef\cmd{...}. I don't understand why the empty \newcommands are there.

One reason I can think of is that \newcommand\cmd checks if \cmd is definable, hence raises error if \cmd is already defined when loading etoolbox. Then, is there some other popular package that provides the same three commands in the history? On the other hand, why these three (and only three) commands are special?

Update: As indicated by @moewe's answer, there are six such commands in total, the other three are firstly defined by \newcommand*\cmd{}. You can use a regular expression like \\newcommand\*?\{\\[a-zA-Z]+\}{} to match all of them.

muzimuzhi Z
  • 26,474

1 Answers1

4

etoolbox uses \newcommand or \newrobustcmd for all user-level commands (to avoid name clashes I presume). Some commands get empty initial definitions with \newcommand{\<cmd>}{} because their real definition needs an \edef or some other treatment that \newcommand doesn't provide. E.g.

\newrobustcmd gets an empty initial definition because it needs to be defined \protected\def

\newcommand*{\newrobustcmd}{}
\protected\def\newrobustcmd{\@star@or@long\etb@new@command}

and \ifdefmacro is ultimately defined via an \edef

\newcommand{\ifdefmacro}{}
\long\edef\ifdefmacro#1{%
  \noexpand\expandafter\noexpand\etb@ifdefmacro
  \noexpand\meaning#1\detokenize{macro}:&}
\edef\etb@ifdefmacro{%
  \def\noexpand\etb@ifdefmacro##1\detokenize{macro}:##2&}
\etb@ifdefmacro{\notblank{#2}}

\protecting needs a #{ type 'argument' (for lack of a better term)

\newcommand*{\protecting}{}
\def\protecting#{%
  \ifx\protect\@typeset@protect
    \etb@protecting\@firstofone
  \fi
  \ifx\protect\@unexpandable@protect
    \etb@protecting\etb@unexpandable
  \fi
  \ifx\protect\noexpand
    \etb@protecting\unexpanded
  \fi
  \ifx\protect\string
    \etb@protecting\detokenize
  \fi
  \relax\@firstofone}

\def\etb@protecting#1#2\relax\@firstofone{\fi#1}
\long\def\etb@unexpandable#1{\unexpanded{\protecting{#1}}}

but many other macros like \ifcsmacro use a \newcommand directly

\newcommand*{\ifcsmacro}[1]{%
  \ifcsdef{#1}
    {\expandafter\ifdefmacro\csname#1\endcsname}
    {\@secondoftwo}}

Internal commands (\etb@...) are usually defined directly with \def/\edef/... and not via \newcommand, because name clashes are not expected there.

moewe
  • 175,683
  • Ah sorry I didn't catch those empty \newcommand all. So, this is for consistently checking naming clash taken by \newcommand (which is equivalent to \ifcsname in the current implementation)? – muzimuzhi Z May 24 '20 at 10:26
  • @muzimuzhiZ That's what I suspect, yes. (Of course I can't be sure why the package author really wrote things that way, but I think it's a fair assumption.) – moewe May 24 '20 at 10:28
  • @muzimuzhiZ Sorry, I only just saw the comment about \ifcsname. I don't quite understand what you mean there. Can you clarify what you meant, please? – moewe May 24 '20 at 10:29
  • In the current implementation of \newcommand, it uses \@ifdefinable, which uses \@ifundefined, which uses \ifcsname to check name clash. So to check name clash, using \newcommand is the same as directly using \ifcsname but with better error message. – muzimuzhi Z May 24 '20 at 10:42
  • @muzimuzhiZ no \ifcsname is true if the csname has a definition so after \let\foo it is true for foo but the latex definition treats relax as undefined and also gives errors for any command that stars with end whether it is defined or not. – David Carlisle May 24 '20 at 10:48
  • @DavidCarlisle Thanks. Follow your comment, I have just read the definition of \@ifdefinable and saw the logic you said. – muzimuzhi Z May 24 '20 at 11:01