How to use \csname, \expandafter, \csuse, \@namedef ...?

Briefly, the reason why your use of \expandafter fails is that you do not have enough of them. For more details see the eloquent explanations of this elsewhere on this site, such as why-do-we-need-eleven-expandafters-to-expand-four-tokens-in-the-right-order.

The issue that you are having with \MakeUppercase is because it is not expandable, which means that you cannot use it directly inside a \csname...\endcsname definition. This is explained in detail in the answers to the post create-a-capitalized-macro-token-using-csname.

Werner has given one solution where he avoids the uppercase issue by passing both the capitalised and uncapitalised name to the macro creator. Here is another solution that "uppercases" the macro name automatically.

Rather than using \providescommand to check to see if the \@my<command> has already been defined I check this directly using \ifcsdef as using \providescommand would require some \expandafters that I would rather avoid. Finally, as a bonus, the code below also defines a "factory function" \@DefineMyCommands for creating a family of such commands from a comma separated list.

\documentclass{article}
\usepackage{etoolbox}

\makeatletter

\newcommand\@DefineMyCommand[1]{\@ReallyDefineMyCommand#1\relax}
\def\@ReallyDefineMyCommand#1#2\relax{%
  % Define command <#1#2> to set @my#1#2={arg of <#1#2>}
  % Here #1 is the first character of the comamnd and #2 is the rest.
  % We have isolated #1 so that we can uppercase it on the next line
  \uppercase{\expandafter\gdef\csname #1}#2\endcsname##1{\@namedef{@my#1#2}{##1}}
  % if \@my#1#2 is not already defined then define it
  \ifcsdef{@my#1#2}{\relax}{\@namedef{@my#1#2}{No \MakeUppercase{#1#2} defined!}}
}
\newcommand{\@DefineMyCommands}{\forcsvlist{\@DefineMyCommand}}% factory function

\@DefineMyCommands{mycmd, test, fred, julie}% define a bunch of macros

\makeatother

\newcommand*\showDF[1]{\csname @my#1\endcsname}

\begin{document}

\showDF{mycmd}

\Mycmd{A command!}

\showDF{mycmd}

\showDF{test}

\Test{A test!}

\showDF{test}

\end{document}

Here is the "expected" output:

enter image description here

Edit Actually, defining a factory command is a little silly if it is only going to be used once. Unless there are a large number of these macros it would be smarter to write

\forcsvlist{\@DefineMyCommand}{mycmd, test, fred, julie}

as this applies the command \@DefineMyCommand to the elements in the list without creating an unnecessary macro.


Here is how you can do this:

\documentclass{article}
\makeatletter
%\providecommand*\@mycmd{No {\MakeUppercase Mycmd} defined}
%\newcommand*\Mycmd[1]{\renewcommand*\@mycmd{#1}}

\newcommand{\@newDF}[2]{%
  \@namedef{@#1}{No \MakeUppercase{#2} defined}%
  %\expandafter\newcommand\csname @#1\endcsname{No \MakeUppercase{#2} defined}% Alternative to above
  \expandafter\newcommand\csname #2\endcsname[1]{%
    \expandafter\renewcommand\csname @#1\endcsname{##1}}
}
\@newDF{mycmd}{Mycmd}
\newcommand*\showDF[1]{\csname @#1\endcsname}
\makeatother
\begin{document}

\showDF{mycmd}\par
\Mycmd{Test}
\showDF{mycmd}

\end{document}

Output is:

No MYCMD defined
Test

The definitions are given with a \show:

> \@mycmd=macro:
->No \MakeUppercase {Mycmd} defined.


> \Mycmd=\long macro:
#1->\expandafter \renewcommand \csname @mycmd\endcsname {#1}.

Tags:

Macros