1

This question is an extension to Defining command part by part thread. Let's say we have a command \foo with 1 argument. Can we produce a command \setfoo{aaa}{<some stuff>} which redefines \foo to: if an argument of \foo is equal to 'aaa', do 'some stuff'; otherwise do what \foo was supposed to do previously? I would also like to use \setfoo many times, so

\setfoo{a}{part1}
\setfoo{b}{part2}
\setfoo{c}{part3}
\foo{a}  \foo{b} \foo{c}

would produce

part1 part2 part3

Additionally, I want to be able to use \setfoo inside enumerate, and then use \foo inside or outside, so

\begin{enumerate}
\item First item. \setfoo{1}{part 1}
\setfoo{2}{part 2}
\item Second item.
\item Some text \setfoo{3}{part 3} Some text \foo{1}
\end{enumerate}
\foo{1} \foo{2} \foo{3}

would give

1. First item.
2. Second item.
3. Some text Some text part 1
part 1 part 2 part 3

I've tried egreg's solution from previous question, but all \foos beyond \end{enumerate} were undefined. MWE:

\documentclass{article}

\newcommand{\setfoo}[2]{%
  \expandafter\newcommand\csname tomasz@#1\endcsname{#2}%
}


\makeatletter
\newcommand{\foo}[1]{%
  \ifcsname tomasz@#1\endcsname
     \expandafter\@firstoftwo
  \else
     \expandafter\@secondoftwo
  \fi
  {\csname tomasz@#1\endcsname}%
  {BUMMER!}%
}
\makeatother

\begin{document}
\begin{enumerate}
\item First item. \setfoo{1}{part 1}
\setfoo{2}{part 2}
\item Second item.
\item Some text \setfoo{3}{part 3} Some text \foo{1}
\end{enumerate}
\foo{1} \foo{2} \foo{3}
\end{document}

This produces:

1. First item.
2. Second item.
3. Some text Some text part 1
BUMMER! BUMMER! BUMMER!

(BUMMER! is the output of \foo{<x>} when \setfoo{<x>}{<something>} was not used before.)

If it makes the problem easier, I could only use numbers as arguments of \foo, but I would appreciate general solution (when arguments of \foo are some strings of letters/numbers).

1 Answer 1

2

Just make the definition global:

\newcommand{\setfoo}[2]{%
  \expandafter\gdef\csname tomasz@#1\endcsname{#2}%
}

\newcommand defines a macro in the current group (thus obviously in child groups). When this group is finished, the definition is lost: the macro definition is restored to the state it had before the group was started. In the case of a successful \newcommand call, the previous state is necessarily “undefined” or \let-equal to \relax, but if \renewcommand is used inside a group, the previous state that is restored when the group ends is the definition the command had before the group started. Example:

\documentclass{article}

\begin{document}

\newcommand{\foo}{outside}

{ \renewcommand{\foo}{inside} \foo }

\foo

\end{document}

Screenshot

In contrast to \newcommand and friends (\newcommand*, \renewcommand, \providecommand, etc.), \gdef updates the meaning of the following token globally: the new definition transcends all groups.

Note that there are a few other differences between \gdef and \newcommand, apart from the syntax:

  • A command defined with \gdef is not \long, unless the definition is like \long\gdef\cmdname〈parameter text〉{〈replacement text〉} (in the example below, you would need to replace \gdef with \long\expandafter\gdef). A \long macro accepts arguments containing \par tokens, whereas other macros throw an error when called with such arguments (the famous “runaway argument” error message). Not being \long is thus more restrictive, but this makes finding errors in one's (La)TeX code easier, assuming one has decided that \par tokens have nothing to do in an argument of the command being defined (which means the command should not be defined as \long; with the LaTeX2e kernel framework, it should be defined with \newcommand*).

  • \gdef happily redefines the same command if asked to, whereas \newcommand would refuse to do so (it would throw an error). This behavior of \gdef may be a desirable feature in your case (?). If not, it's easy to add a check and error out in case the command is already defined.

Full code of your example with the \gdef fix:

\documentclass{article}

\newcommand{\setfoo}[2]{%
  \expandafter\gdef\csname tomasz@#1\endcsname{#2}%
}


\makeatletter
\newcommand{\foo}[1]{%
  \ifcsname tomasz@#1\endcsname
     \expandafter\@firstoftwo
  \else
     \expandafter\@secondoftwo
  \fi
  {\csname tomasz@#1\endcsname}%
  {BUMMER!}%
}
\makeatother

\begin{document}
\begin{enumerate}
\item First item. \setfoo{1}{part 1}
\setfoo{2}{part 2}
\item Second item.
\item Some text \setfoo{3}{part 3} Some text \foo{1}
\end{enumerate}
\foo{1} \foo{2} \foo{3}
\end{document}

Screenshot

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .