9

In this question I have already been given a sophisticated way of achieving what I set out to do. This question is not a "how do I do this" question - I already have an answer to that - it is a "explain for me what is the flaw in the approach I attempted" question.

The original problem was that the \crefname macro provided by the cleveref package was not having any effect within a \foreach. It was pointed out by jfbu (in an answer) and egreg (in a comment) that the problem was that \crefname has an effect only locally, and the changes are discarded at the end of every loop iteration. In the time before egreg posted an answer providing a solution, it occurred to me to use the \foreach to build a command to perform the \crefname changes, then call that command after the loop had ended. I used the \global macro to make the changes global. Unfortunately, the \global causes error messages to appear, and I don't know why. Here is my attempt:

\documentclass[a4paper]{amsart}

\def\myTheoremEnvironments{%
    theorem/theorems,%
    cobblestone/cobblestones,%
    toybox/toyboxes%
}

\usepackage{tikz, titlecaps, cleveref}

% Number all definition, theorem, etc. environments using the same counter.
% Start counting again at the start of each section.
% Style these environments using LaTeX's "definition" style.
\theoremstyle{definition}
\newtheorem{baseTheorem}{Base Theorem}[section]
\def\mycrefnamecommands{}
\foreach \x/\y in \myTheoremEnvironments {
    \edef\tmp{\noexpand\newtheorem{\x}[baseTheorem]{\noexpand\titlecap{\x}}}\tmp
    \global\edef\mycrefnamecommands{\mycrefnamecommands\noexpand\crefname{\x}{\x}{\y}}
}
\mycrefnamecommands

\begin{document}

\section{Hello, and welcome to my document.}

\begin{cobblestone}
    \label{csref}
    Hello. I am an ``cobblestone'' environment.
\end{cobblestone}

\begin{toybox}
    \label{tbref}
    I am a ``toybox'' environment. What a strange name that is for an environment.
\end{toybox}

\begin{theorem}
    \label{thmref}
    People trying to do strange things with ``foreach'' should expect trouble.
\end{theorem}

Here are some references to \cref{csref}, \cref{tbref} and \cref{thmref}.
I could have tried using just one call to ``cref'' here,
but it's best not to run before you can walk.

\end{document}

Here is the error message. I tried googling it, but I don't see any particularly useful results.

! Argument of \@ynthm has an extra }.
<inserted text> 
                \par 
l.66 }
  • \gdef is global \def, \xdef is global \edef – percusse Nov 16 '13 at 20:43
  • I tried changing the \global\edef to \xdef, it results in the same error message. – Hammerite Nov 16 '13 at 20:46
  • This is a good attempt (weren't it for the small glitch). I'd prefer my expl3 solution, though. ;-) – egreg Nov 16 '13 at 20:58
10

You have to remember that \edef expands all the way, but \noexpand only inhibits expansion once. So after the first cycle the replacement text of \mycrefnamecommands is

\crefname{theorem}{theorem}{theorems}

and at the second cycle \edef will try to expand \crefname, causing a disaster.

You have to be careful and only expand once \mycrefnamecommands in the various steps. This can be done in two ways:

\toks0=\expandafter{\mycrefnamecommands}%
\xdef\mycrefnamecommands{\the\toks0 \noexpand\crefname{\x}{\x}{\y}%

using the fact that token lists resulting from \the\toks0 is not further expanded by \edef (\xdef is the same as \global\edef).

Another way, that exploits e-TeX extensions (which are available in LaTeX) is

\xdef\mycrefnamecommands{%
  \unexpanded\expandafter{\mycrefnamecommands}%
  \noexpand\crefname{\x}{\x}{\y}%
}%

which essentially does the same as above, but in only one step.

A different strategy could be

\def\mycrefnamecommands{}
\foreach \x/\y in \myTheoremEnvironments {
  \let\crefname\relax
  \edef\tmp{\noexpand\newtheorem{\x}[baseTheorem]{\noexpand\titlecap{\x}}}\tmp
  \xdef\mycrefnamecommands{\mycrefnamecommands\crefname{\x}{\x}{\y}}
}
\mycrefnamecommands

In the \foreach cycle we set \crefname equal to \relax, so it becomes unexpandable and so it will not be interpreted in any way by \xdef; however, at the end of \foreach, \crefname will have its original meaning back, hence at the time of the execution of the final \mycrefnamecommands macro it will do its job regularly.

  • You have so many ideas on how to do things. I feel like I am getting better at understanding TeX's macro expansion behaviour, but I have a long way to go before I understand it like you :) – Hammerite Nov 16 '13 at 21:00
  • @Hammerite Well, you were a baby when I started using TeX. ;-) – egreg Nov 16 '13 at 21:03
  • 2
    @Hammerite Here are some tools that helped me when I was getting started: Put \errorcontextlines=100 at the start of the file to get a better idea where a macro goes bad (not much help for this one). Put \show\mycrefnamecommands just before and after the global\edef is applied to it. You can thus see just what TeX has done to it. On the second pass you will see that the \noexpand was not retained and then the error message from the expansion of \crefname. – Dan Nov 16 '13 at 21:14

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.