# Creating commands with a TeX for loop

I am trying to create many different commands based on a single command (essentially a custom header) in a predictable way. Is there a way to do this in TeX/LaTeX?

# My specific example:

I'm writing a module specification, where each of the sub headings is denoted by \modulespec{description}{the description} or \modulespec{learning outcomes}{the learning outcomes} etc. I am hoping that TeX can generate commands like \mdescription{the description} and \mlearningoutcomes{the learning outcomes}

I know that in bash-like expansion combined with TeX I would write

for i in 'description' 'learning outcomes'
do
\def\csname m\getRidOfSpaces{$i} \endcsname{\modulespec{$i}{#1}}
done


(if you'll excuse the combination of languages) where \getRidOfSpaces changes learning outcomes to learningoutcomes. Is this possible/feasible with TeX/LaTeX?

M(not)WE:

\documentclass{article}
\usepackage{blindtext}

% In the real thing, this definition is much more complicated
\newcommand{\modulespec}[2]{\section{#1} #2}

% The code I want in pure TeX/LaTeX
for i in 'description' 'learning outcomes'
do
\def\csname m\getRidOfSpaces{$i} \endcsname{\modulespec{$i}{#1}}
done
% end of non TeX code

\begin{document}

% Both work
\modulespec{description}{\blindtext}
\modulespec{learning outcomes}{\blindtext}

% These two commands should produce the same output as the previous two
\mdescription{\blindtext}
\mlearningoutcomes{\blindtext}

\end{document}

-
Is removing the spaces an absolute requirement? It's (marginally) easier if we don't have to –  Joseph Wright Mar 6 '14 at 11:55
Please have a look on the forloop package, but I am not sure whether it allows for generation of commands inside. Please add a MWE also –  Christian Hupfer Mar 6 '14 at 11:55
@JosephWright Removing the spaces is not an absolute requirement, I think there are few enough commands to define them by hand. –  Nathanael Farley Mar 6 '14 at 12:05
@ChristianH. I'm afraid I don't know enough TeX to produce anything working. I can add a minimal document to the question if that helps? –  Nathanael Farley Mar 6 '14 at 12:06

Here's a LaTeX3 implementation.

\documentclass{article}
\usepackage[margin=1cm]{geometry} % to fit in one page
\usepackage{blindtext}
\usepackage{xparse}

\newcommand{\modulespec}[2]{\section{#1} #2}

\ExplSyntaxOn

\NewDocumentCommand{\definemodules}{m}
{
\farley_definemodules:n { #1 }
}

\tl_new:N \l_farley_temp_tl
\cs_new_protected:Npn \farley_definemodules:n #1
{
\clist_map_inline:nn { #1 }
{
\tl_set:Nn \l_farley_temp_tl { ##1 }
\tl_replace_all:Nnn \l_farley_temp_tl { ~ } { }
\cs_new_protected:cpn { m \l_farley_temp_tl } { \modulespec { ##1 } }
}
}

\ExplSyntaxOff

\definemodules{
description,
learning outcomes
}

\begin{document}

\modulespec{description}{\blindtext}
\modulespec{learning outcomes}{\blindtext}

% These two commands should produce the same output as the previous two
\mdescription{\blindtext}
\mlearningoutcomes{\blindtext}

\end{document}


We define a user level macro for defining all modules in one swoop; this is \definemodules that simply hands control to the programmer's level function \farley_definemodules:n (this is the best practice according to LaTeX3 programming guidelines).

Now the function \farley_definemodules:n splits its argument at commas and does a loop over the items. The current item is available as #1, but we need ##1 because we're inside a definition.

Each cycle stores the item in the scratch token list variable \l_farley_temp_tl so that we can apply \tl_replace_all:Nnn to it. The replacement is “any space is replaced by nothing”; in the programming environment spaces are ignored, so a “real space” is denoted by ~.

Now we use the \cs_new_protected:cpn function that's the analog of the old style \@namedef: the first argument is made into a control sequence name with \csname...\endcsname (the token list is expanded here by TeX rule).

-
This is impressive! Could you explain a little how the code logic works? I've never seen anything like this before. –  Nathanael Farley Mar 6 '14 at 14:46

It looks like you want to do a 'for each' loop. The LaTeX kernel has a macro \@for which does this for each element of a comma-separated list. You also need \zap@space or similar to remove spaces. For example:

\newcommand{\modulespec}[2]{\section{#1} #2}
\makeatletter
\@for\@tempa:=description,learning outcomes\do{%
\edef\@tempa
{\expandafter\expandafter\expandafter\zap@space
\expandafter\@tempa\space\@empty}%
\expandafter\edef\csname m\@tempa\endcsname
{\noexpand\modulespec{\@tempa}}%
}
\makeatother


will define \mdescription and \mlearningoutcomes as required. I've used an \edef to force expansion of the variable in the correct places. (No use applying \protected@edef here as the 'text' has to be 'safe' inside \csname.)

-
It would be good if the title remained with the two words separated. I think one possible way is by \let\@tempb\@tempa at the start and then putting \modulespec{\@tempb} instead? –  Nathanael Farley Mar 6 '14 at 13:03
@NathanaelFarley If you use two separate words you can't easily them as a document-level command –  Joseph Wright Mar 6 '14 at 13:52

Using \xintFor and the LaTeX2e kernel utilities (with an @ in their names):

\documentclass{article}
\usepackage[english]{babel}
\usepackage{blindtext}
\usepackage{xinttools}
% In the real thing, this definition is much more complicated
\newcommand{\modulespec}[2]{\section{#1} #2}

% % The code I want in pure TeX/LaTeX
% for i in 'description' 'learning outcomes'
% do
%     \def\csname m\getRidOfSpaces{$i} \endcsname{\modulespec{$i}{#1}}
% done
% % end of non TeX code

% Nota bene: in the above you probably meant \long\def, not \def
\makeatletter
\xintFor #1 in {description, learning outcomes}\do
{\long\@namedef{m\zap@space #1 \@empty}##1{\modulespec{#1}{##1}}}
\makeatother

\begin{document}

% Both work
\modulespec{description}{\blindtext}
\modulespec{learning outcomes}{\blindtext}

% These two commands should produce the same output as the previous two
\mdescription{\blindtext}
\mlearningoutcomes{\blindtext}

\end{document}


-
the \long\@namedef is not quite a \newcommand as it doesn't check for preexisting command name. –  jfbu Mar 6 '14 at 14:09