3

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. :)

1

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>}
  • 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 '16 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 '16 at 21:52
  • @Iago-lito For that one can use a variant. Let me add it. – egreg Mar 16 '16 at 22:40
2

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}
  • 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 – David Carlisle Mar 16 '16 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 '16 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 – David Carlisle Mar 16 '16 at 20:02

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.