5

I have already seen these posts Macro defining macro and
Macros that define other macros, which also define other macros but unfortunately I didn't manege to figure it out how to correct my attempt to the problem. I want to create a macro which will produce macro + global macro with @ in front of macro name, like in the following example:

 \def\Bob#1{\gdef\@Bob{#1}}
 \Bob{My name is Bob}
 \makeatletter
 \@Bob % it will display `My name is Bob'
 \Bob{Name: Bob}
 \@Bob % it will display `Name: Bob'
 \makeatother

So this works. However:

 \def\DefineField#1{%
     \expandafter\edef\csname #1\endcsname##1{%
         \gdef\csname @#1 \endcsname{##1}%
     }%
 }
 \DefineField{Alice}
 \Alice{My name is Alice}

doesn't work. I got Undefined control sequence. with \@Alice. I have checked Alice and Bob with \show command:

> \Bob=macro:
#1->\gdef \@Bob {#1}.
l.20 \show\Bob
> \Alice=macro:
#1->\gdef \@Alice  {#1}.
l.21 \show\Alice

They have exactly the same structure. What did I miss? MWE:

\documentclass[preview]{standalone}
\makeatletter

\def\DefineField#1{%
    \expandafter\edef\csname #1\endcsname##1{%
        \gdef\csname @#1 \endcsname{##1}%
    }%
}
\DefineField{Alice}
\Alice{My name is Alice}


\def\Bob#1{\gdef\@Bob{#1}}
\Bob{My name is Bob}

\begin{document}
%\@Alice  % Undefined control sequence.
\@Bob
\Bob{No my name is Alice}

\@Bob
\show\Bob
\show\Alice
\makeatother
\end{document}
  • 1
    Remove the space after the #1 in \gdef\csname @#1 \endcsname{##1}%, it should be \gdef\csname @#1\endcsname{##1}% – moewe Jan 27 at 11:00
  • Thank you very much, I don't know why I missed it. – andywiecko Jan 27 at 11:05
8

The definition of \DefineField has a superfluous space in \gdef\csname @#1 \endcsname{##1}%, it should be

\def\DefineField#1{%
  \expandafter\edef\csname #1\endcsname##1{%
    \gdef\csname @#1\endcsname{##1}%
  }%
}

With the space you would define the command \@Alice␣ (where represents a space) as can be verified by checking \@Alice␣'s definition with

\expandafter\show\csname @Alice \endcsname

If we want to define \@Alice we need to get rid of the space.


Note that the use of \edef in \DefineField can have unintended consequences, so you may want to consider going for the solution presented in Martin Scharrer's answer or egreg's answer.

6

I would not use \edef here but work with a second \expandafter.

The main problem here is that you have a space between #1 and \endcsname, so you actually define \@Alice<space>, which is possible with \csname.

Correct code is:

\def\DefineField#1{%
    \expandafter\def\csname #1\endcsname##1{%
        \expandafter\gdef\csname @#1\endcsname{##1}%
    }%
}
6

You're defining \csname @Alice \endcsname which results in a control sequence token having a trailing space in its name. The space after \csname doesn't find its way during tokenization, because it follows a control word, but the space between #1 and \endcsname will not be removed.

Your usage of \edef is quite problematic as well: if you mistakenly use \DefineField{Alice} twice,

\DefineField{Alice}
\Alice{My name is Alice}
\show\Alice

\DefineField{Alice}
\show\Alice

you'll get

> \Alice=macro:
#1->\gdef \@Alice {#1}.

> \Alice=macro:
#1->\gdef My name is Alice{#1}.

This can be cured by checking first if \Alice is defined:

\def\DefineField#1{%
  \@ifundefined{#1}
    {% go on, it's undefined
      \expandafter\edef\csname #1\endcsname##1{%
        \gdef\csname @#1\endcsname{##1}%
      }%
    }
    {% ignore the redefinition
      \PackageWarning{andywiecko}{%
        \expandafter\noexpand\csname #1\endcsname already defined%
      }%
    }%
}

A simpler approach with xparse and expl3:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\DefineField}{m}
 {
  \cs_new_protected:cpn { #1 } ##1
   {
    \cs_new_protected:cpn { @#1 } { ##1 } % or `\cs_new:cpn
   }
 }

\ExplSyntaxOff

\DefineField{Alice}
\Alice{My name is Alice}

\expandafter\show\csname @Alice\endcsname

This will produce a standard error if \DefineField{Alice} is used twice or if you try doing \DefineField{box}; the code will show

> \@Alice=\protected\long macro:
->My name is Alice.

You may want to use \cs_new:cpn in the second instance, depending on what expansion context you will use the \@Alice macro.


A more proper expl3 interface, where instead of defining \@foo macros you do \UseField{foo} when needed. A way to override already existing fields is provided.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\prop_new:N \g_andywiecko_fields_prop
\cs_new:Nn \andywiecko_define_field:nn
 {
  \prop_gput:Nnn \g_andywiecko_fields_prop { #1 } { #2 }
 }

\NewDocumentCommand{\DefineField}{sm}
 {
  \cs_set_protected:Nn \__andywiecko:n
   {
    \andywiecko_define_field:nn { #2 } { ##1 }
   }
  \IfBooleanTF { #1 }
   {% override an existing definition
    \cs_gset_eq:cN { #2 } \__andywiecko:n
   }
   {% new field
    \cs_new_eq:cN { #2 } \__andywiecko:n
   }
 }

\NewExpandableDocumentCommand{\UseField}{m}
 {
  \prop_item:Nn \g_andywiecko_fields_prop { #1 }
 }

\ExplSyntaxOff

\DefineField{author} % gives error

\DefineField*{author} % no error, use new interface

\DefineField{Alice}
\Alice{My name is Alice}

\DefineField{Alice} % gives error
  • Yours comments, especially about double usage of \DefineField{Alice}, are very useful, thanks! – andywiecko Jan 27 at 11:49
1

In the other answers the spurious space within the \csname..\endcsname-expression is already pointed out.

In case you wish to avoid typing so many \csname..\endcsname-expressions, I can offer a macro \name.

\name takes following tokens that are not nested in braces for its first argument and following tokens nested in braces for its second argument and applies \csname..\endcsname to its second argument.
The tokens that form the first argument can, e.g., be: \global\long\outer\def or \newcommand or \string or \meaning or whatever. You can also leave the first argument empty.

Examples:

\name\show{foo} yields: \show\foo

\name\def{foo}... yields: \def\foo...

\name\global\long\outer\def{foo}... yields: \global\long\outer\def\foo...

\name\newcommand{foo}... yields: \newcommand\foo...

\name{foo}... yields: \foo

\name\name\global\let{foo}={bar}
yields:
\name\global\let\foo={bar}
yields:
\global\let\foo=\bar

\documentclass{article}

\newcommand\UDExchange[2]{#2#1}
\newcommand\name{}\long\def\name#1#{\romannumeral0\innername{#1}}%
\newcommand\innername[2]{%
  \expandafter\UDExchange\expandafter{\csname#2\endcsname}{ #1}%
}%

\makeatletter
\newcommand\novalueprovided{\nfss@text{\reset@font\bfseries??}}
\makeatother

\newcommand\DefineField[2][\novalueprovided]{%
  \name\newcommand{#2}[1]{\name\gdef{@#2}{##1}}%
  \name\newcommand{@#2}{}%
  \name{#2}{#1}%
}%

\newcommand\UseField[1]{\name{@#1}}%

\DefineField[initial value of field Alice]{Alice}

\DefineField{Bob}

\begin{document}

{\tt\frenchspacing
\name\string{Alice}: \name\meaning{Alice}

\name\string{@Alice}: \name\meaning{@Alice}

\name\string{Bob}: \name\meaning{Bob}

\name\string{@Bob}: \name\meaning{@Bob}

\verb|\UseField{Alice}:| \UseField{Alice}

\verb|\UseField{Bob}:| \UseField{Bob}
}

\hrulefill

Now the fields are changed:

\Alice{My name is Alice}

\Bob{My name is Bob}

{\tt\frenchspacing
\name\string{Alice}: \name\meaning{Alice}

\name\string{@Alice}: \name\meaning{@Alice}

\name\string{Bob}: \name\meaning{Bob}

\name\string{@Bob}: \name\meaning{@Bob}

\verb|\UseField{Alice}:| \UseField{Alice}

\verb|\UseField{Bob}:| \UseField{Bob}
}

\end{document}

If you provide more info about what you try to achieve in practice, an interface could be developed which also does better error-management.

enter image description here

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.