4

I am trying to define multiple, sophisticated newcommands within a foreach loop. This sounds so simple, but why is it so hard ?

Naive attempt:

\documentclass{report}

\usepackage{tikz}

\foreach \i in {first,second,third}{
    \expandafter\newcommand\csname\i\endcsname[3]{ % IYO: how readable is this "naive try"?

        command name: \i

        \begin{minipage}[\textwidth]
        first argument: ##1

        second argument: ##2

        third argument: ##3
        \end{minipage}
    }
}

\begin{document}

\first{1}{2}{3}\\

\second{a}{b}{c}\\

\third{Is}{\LaTeX}{cool?}\\

\end{document} 

this spits:

! Undefined control sequence.
l.22     \first
               {1}{2}{3}\\

okay, \newcommand does not define the command outside the scope of \foreach. Does adding \global anywhere help? If it does, it could not figure out where to put it. (basic LaTeX rule of thumb: nothing is intuitive)

I must admit that it has been a headache. Luckily, Internet is over there, so thank you guys and the Stack websites. Main traps on the way:

  • \global seems to work only with \def so use \gdef instead of \newcommand
  • \i will not be expanded as <the loop counter> but as \iota, so use \edef so use \xdef
  • the \def family does not handle arguments specifications the same way, so use ##1##2##3 instead of [3] (this is looks soOo much more nice, readable and easy to maintain. I love ##1##2##3. The day I'll need 5 arguments instead of 3, all I'll have to do is just.. never mind.)
  • .. I did not even dare asking for optional arguments support. After all, this is only 2016.

In the end, this does compile:

\documentclass{report}

\usepackage{tikz}

\foreach \i in {first,second,third}{
    \expandafter\xdef\csname\i\endcsname##1##2##3{ % yum

        command name: \i

        % \begin{minipage}[\textwidth]
        first argument: ##1

        second argument: ##2

        third argument: ##3
        % \end{minipage}
    }
}

\begin{document}

\first{1}{2}{3}\\

\second{a}{b}{c}\\

\third{What}{a}{pain!}\\

\end{document}

.. which gets me:

enter image description here

However, it does not work anymore if I uncomment the lines concerning the minipage environment:

! Use of \@iminipage doesn't match its definition.
\IfFileExists #1#2#3->\openin \@inputcheck #1 
                                              \ifeof \@inputcheck \ifx \inpu...
l.18     }

(by the way, what an impressive clue. Thank you, compiler. Maybe I should make my mind more rigid or there is a danger that you and I will fuse together in harmony, never to be two again.)

In a nutshell, considering the situation:

  • Why does minipage not work? How may I get it working?
  • Is there a way I could ever have \first, \second and \third supporting optional arguments?
  • Am I reinventing the wheel? Investigating the wrong way? Is there a better way to do this?

This is it. Thank you for you patience.

Thank you for being here. :)

2 Answers 2

2

You're using the wrong tool, in my opinion:

\documentclass{report}

\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\xforeach}{s m +m}
 {
  \IfBooleanTF{#1}
   {
    \clist_map_inline:on { #2 } { #3 }
   }
   {
    \clist_map_inline:nn { #2 } { #3 }
   }
 }
\cs_generate_variant:Nn \clist_map_inline:nn { o }
\ExplSyntaxOff

\xforeach{first,second,third}
 {
  \expandafter\newcommand\csname#1\endcsname[3]{%is this "naive try"?
    \par\bigskip
    command name: #1
    \par\nopagebreak\medskip
    \begin{minipage}{.5\textwidth}
      first argument: ##1

      second argument: ##2

      third argument: ##3
    \end{minipage}
  }
 }

\setlength{\parindent}{0pt} % just for the example

\begin{document}

\first{1}{2}{3}

\second{a}{b}{c}

\third{Is}{\LaTeX}{cool?}

\end{document} 

As you see, the current item being processed is denoted by #1 instead of \i and ##1 can be used as usual for inner arguments.

enter image description here

If you need to use a macro expanding to a list, use \xforeach*:

 \newcommand{\mylist}{first,second,third}

 \xforeach*{\mylist}{<whatever>}
3
  • Great ! I already fell in love with xparse once because it was easy building optional arguments and their defaults with it. I am so glad there is a dedicated tool to achieve what I needed. Thank you :)
    – iago-lito
    Mar 16, 2016 at 19:59
  • I have troubles when my list needs to be expanded, like if \newcommand{\commands}{first,second,third} then \xforeach{\commands}{...}. In this case, I use \clist_map_inline:Nn.
    – iago-lito
    Mar 16, 2016 at 21:52
  • @Iago-lito For that one can use a variant. Let me add it.
    – egreg
    Mar 16, 2016 at 22:40
3

You need to protect \begin from expanding in the \xdef, and minipage width needs {} not [] other than that you were nearly there, I also fixed up some white space issues.

\documentclass{report}

\usepackage{tikz}

\foreach \i in {first,second,third}{%
    \expandafter\xdef\csname\i\endcsname##1##2##3{% yum

        command name: \i

        \noindent\noexpand\begin{minipage}{\textwidth}
        first argument: ##1

        second argument: ##2

        third argument: ##3
        \noexpand\end{minipage}%
    }%
}

\begin{document}


\first{1}{2}{3}

\second{a}{b}{c}

\third{What}{a}{pain!}

\end{document}

Probably I'd use a different loop macro that doesn't introduce unwanted groups:

\documentclass{report}

% \usepackage{tikz}

\newcommand\dodefs[1]{%
    \expandafter\newcommand\csname#1\endcsname[3][?]{% yum

        command name: #1

        \noindent\begin{minipage}{\textwidth}
        first argument: ##1

        second argument: ##2

        third argument: ##3
        \end{minipage}%
  }%
}

\makeatletter

\@for\i:=first,second,third\do{%

\expandafter\dodefs\expandafter{\i}

}
\makeatother

\begin{document}

%\show\first
\first[1]{2}{3}

\first{2}{3}

\second[a]{b}{c}

\third[What]{a}{pain!}

\end{document}
3
  • I'd use a loop macro that does not group the body, (such as \@for that is defined in the latex format) then for optional arguments you could use \newcommand Mar 16, 2016 at 17:17
  • Well, thank you. I am now learning about \noexpand and \@for. However, I could not compile your second code, getting: > \first=macro: ->\@protected@testopt \first \\first {?}. l.31 \show\first with pdflatex. What's wrong?
    – iago-lito
    Mar 16, 2016 at 19:54
  • @Iago-lito oh nothing that is just \show showing the definition of \first it's a debuuging print statement, just delete that line, or just hit return and tex will carry on, I'll remove it from the above Mar 16, 2016 at 20:02

You must log in to answer this question.

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