
Quería automatizar un poco mi vida y descubrí que el enumitem
paquete proporciona una manera de crear listas personalizadas que pueden continuar contando con respecto a la lista anterior. Sin eso, tendría que manipular explícitamente el enumi
contador \setcounter
y eso puede resultar doloroso, porque es fácil no contar correctamente. :-) De todos modos, con enumitem
, el conteo continuo funciona cuando lo uso resume*
. Sin embargo, al hacerlo, rompe las referencias con Cleverref. Al hacer referencia a un elemento de esa lista, resume,,
se imprime un texto adicional. ¿Cómo puedo deshacerme de esto? ¿Qué estoy haciendo mal en el preámbulo o se trata de algún tipo de error?
\documentclass[a4paper,english]{article}
\usepackage{enumitem}
\usepackage{amssymb}
\usepackage{cleveref}
% defining theorem environments for their specialized versions
\makeatletter
\newenvironment{base@thm}[3]%
{$\;$\linebreak\noindent\mbox{$\triangleright\;\textsc{\textbf{\large #1} #2 (#3).}$}}
{\hfill$\;$\linebreak}
\makeatother
% lemma environments take a label and a name
\newcounter{lemma}
\crefname{lemma}{lemma}{lemmata}
\makeatletter
\newenvironment{lemma}[2]%
{\refstepcounter{lemma}\begin{base@thm}{Lemma}{\thelemma}{#2}\label[lemma]{lemma:#1}\def\@currentlabel{#2}\label{t@lemma:#1}}
{\end{base@thm}\noindent}
\makeatother
% referencing a lemma
\newcommand{\lemmaref}[1]{\Cref{lemma:#1}~(\ref{t@lemma:#1})}
\newcommand{\lemref}[1]{\Cref{lemma:#1}}
\newlist{passumptionslist}{enumerate}{1}
\setlist[passumptionslist]{label=(\alph*)}
\crefalias{asm}{passumptionslisti}
\crefalias{goal}{enumi}
\crefname{asm}{assumption}{assumptions}
\crefname{goal}{goal}{goals}
\newenvironment{assumptions}
{\begin{passumptionslist}[label=(\alph*)]
}
{
\end{passumptionslist}
}
\newenvironment{goals}
{\begin{enumerate}[label=(\roman*)]
}
{
\end{enumerate}
}
\newcommand{\IH}[1][]{I{\kern-1.5pt}H#1}
\newenvironment{passumptions}[1]
{%
\begin{passumptionslist}[resume*,label={$\left(#1_{\arabic*}\right)$}]%
}
{%
\end{passumptionslist}%
}
% individual cases
\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {$#2$}}
\newcommand{\goal}[2]{\item\label[goal]{goal:#1} {$#2$}}
\newcommand{\asmref}[1]{\Cref{asm:#1}}
\newcommand{\goalref}[1]{\Cref{goal:#1}}
\newenvironment{proof}[1][\textbf{Proof}]%
{\restartlist{passumptionslist}\par\noindent\ignorespaces\mbox{\textsc{#1}.}$\;$\\\noindent}
{$\;$\hfill$\square$\\\ignorespacesafterend}
\begin{document}
\begin{lemma}{lemma}{Some Lemma Name}
If
\begin{assumptions}
\asm{lemma}{a}
\end{assumptions}
then
\begin{goals}
\goal{lemma}{b}
\end{goals}
\end{lemma}
\begin{proof}
Unfold \goalref{lemma} and introduce the assumptions.
\begin{passumptions}{H}
\asm{lemma0}{v\Downarrow b}
\end{passumptions}
What is left to show is:
\begin{goals}
\goal{lemma0}{c}
\end{goals}
From \asmref{lemma0}, get:
\begin{passumptions}{H}
\asm{lemma1}{a=v}
\end{passumptions}
Rewrite \asmref{lemma} and \asmref{lemma0} using \asmref{lemma1}:
\begin{passumptions}{H}
\asm{lemmap}{v=c}
\asm{lemma0p}{a\Downarrow b}
\end{passumptions}
Use \asmref{lemma0p}:
\begin{passumptions}{H}
\asm{lemma2}{a=b}
\end{passumptions}
Finally, rewrite \goalref{lemma0} via \asmref{lemma1}.
Then, \asmref{lemma2} solves it.
\end{proof}
\end{document}
Respuesta1
Esta respuesta aborda por qué resume,,
se imprime.
Fundamentalmente hay dos cosas que se topan.
Las [resume*]
listas se rompen cuando no hay antecedentes
Puede ver este comportamiento con el siguiente MWE simple:
\documentclass{article}
\usepackage{enumitem}
\begin{document}
\begin{enumerate}[resume*]
\item Test
\end{enumerate}
\end{document}
¿Por qué se rompe esto? Para habilitarlo resume*
, el enumitem
código debe pasar la configuración de la lista desde la emisión anterior de la lista hasta la próxima vez que se llame a la lista. Lo hace estableciendo \enit@resumekeys@<list>
(donde <list>
se reemplaza por el nombre de la lista, es decir, enumerate
o passumptionslist
) en (esencialmente) lo que se pasó como argumento opcional a la enumitem
lista.Esto se realizaría al final de la lista., entonces cuando \end{enumerate}
se encuentra en el ejemplo anterior. YNose crea el valor predeterminado; entonces, antes de llamar a la lista por primera vez, la macro \enit@resumekeys@<list>
esindefinido.
Ahora, cuando resume*
se pasa a una lista,enumitem
intenta cargar la configuración de la última llamada a la lista y, por lo tanto, se asoma a \enit@resumekeys@<list>
. En particular, lo hace mediante una \csname ... \endcsname
construcción, por lo que cuando \enit@resumekeys@<list>
no está definido, esto no produce errores (con el error de macro no definido). Cuando utilizas \csname ... \endcsname
para acceder a una macro indefinida, siempre devuelve \relax
.
Para permitir que las claves de la llamada actual anulen las claves especificadas previamente,enumitem
anteponeesto a los argumentos opcionales. Entonces, después de procesar resume*
en el MWE, el argumento opcional efectivo se convierte en \relax,resume
.
Pero ahora, para procesar la lista de opciones,enumitem
es necesario procesar una lista separada por comas, un elemento a la vez. Lo hace mediante un truco estándar. Define una macro usando
\def\enitkv@do#1,{%
\ifx\relax#1\empty\else
\enitkv@split#1==\relax %% This processes the key, ignore it for now
\expandafter\enitkv@do\fi}
La macro está delimitada por una coma. Entonces, cuando se le proporciona una lista separada por comas, consumirá la siguiente cadena de caracteres hasta la siguiente ,
; procesará esa cadena de caracteres, la descartará (y la coma) y hará lo mismo nuevamente para el siguiente argumento.
¿Cómo sabe esta macro detenerse? Se detiene cuando se encuentra \relax
. Y en la práctica, esta macro se llama a través de
\enitkv@do#2,\relax,
¿Dónde #2
está la lista de argumentos opcionales? Ahora ves el problema,cuando usasresume*
sin antecedente, la macro anterior, cuando se llama, procesa
\enitkv@do\relax,resume,\relax,
Cuando llega el primero \relax
, la macro termina. Así que lo que nos queda es un colgante
resume,\relax,
Dado que estos no son argumentos para ninguna macro, se procesan como texto. Y como \relax
es una operación no operativa, cuando se imprime esto se convierte en
resume,,
que ves.
Grupos
Pero dices, espera un segundo, también has definido elassumptions
entorno en términos de passumptionslist
y lo has llamadoantesllamando al primero passumptions
en su código. ¿No significa esto que debería haber un antecedente?
Con lo cual debes comparar los siguientes dos MWE.
MWE 1 (sin grupo)
\documentclass{article}
\usepackage{enumitem}
\begin{document}
\begin{enumerate}
\item Test
\end{enumerate}
\begin{enumerate}[resume*]
\item Test
\end{enumerate}
\end{document}
MWE 2 (con grupo)
\documentclass{article}
\usepackage{enumitem}
\begin{document}
\bgroup
\begin{enumerate}
\item Test
\end{enumerate}
\egroup
\begin{enumerate}[resume*]
\item Test
\end{enumerate}
\end{document}
MWE 1 se comportará correctamente, pero MWE 2 se comportará incorrectamente. Entonces, ¿qué está pasando aquí?
La siguiente es una característica de enumitem
, aunque no estoy seguro de que sea una buena idea. Al guardar las opciones actuales en una lista, enumitem
se comporta de manera diferente en cuatro casos diferentes.
- Cuando tiene una
series
opción que no es aresume
o aresume*
, el código guarda tanto el valor del contador actual como los argumentos opcionales actuales usando\global\def
. - Cuando tiene una
series
opción que también es aresume
o aresume*
, el código guarda solo el contador a través de\global\def
, pero no guarda los argumentos opcionales actuales. - Cuando no tiene un
series
, pero tiene unresume*
, el código guarda solo el contador a través de\global\def
, pero no guarda los argumentos opcionales. - Cuando tienes una lista simple que no es ni a
series
ni aresume*
(pero, curiosamente,resume
aquí está bien), el código guarda tanto el contador como los argumentos opcionales usando\def
.
Esto provoca varios efectos secundarios interesantes.
- Si incluye
\begin{enumerate}[resume]...\end{enumerate}
a\newenvironment
, se activa el cuarto caso. Pero a medida que los entornos se agrupan,\def
no se propaga fuera de los entornos. Esto explica por qué la numeración es incorrecta, como observó eneste comentario. - Usando su código original como referencia, tal como lo envolvió
\begin{passumptionslist}...\end{passumptionslist}
en elassumptions
entorno definido mediante\newenvironment
, cuando llama\begin{assumptions}...\end{assumptions}
, el paquete intenta guardar el contador actual y las opciones de lista en\end{passumptionslist}
, pero como estamos en el caso 4, estas se crean a través de definiciones locales, por lo que tan pronto como golpeamos,\end{assumptions}
estos se pierden.
El primer problema identificado anteriormente es un caso extremo un poco extraño: el uso normal de enumitem
no debería llamar [resume*]
a una lista que no se haya utilizado previamente. Dicho esto, este caso límite se puede solucionar simplemente usando un token diferente para demarcar el final de la lista en \def\enitkv@do#1,
.
De hecho, la segunda cuestión está documentada (aunque algo mal). En la versión actual de la documentación de enumitem, es decir resume
local se menciona de pasada en la sección 3.3. Aunque esta extraña interacción con resume*
no está claramente documentada (y definitivamente debería estarlo).
Respuesta2
Creo que el uso de un subyacente passumptionslist
que se incluye en varios entornos es innecesario. Es mejor definir todo como \newlist
s.
%%%% UNTIL ANNOUNCED, SAME AS YOUR CODE %%%%
\documentclass[a4paper,english]{article}
\usepackage{enumitem}
\usepackage{amssymb}
\usepackage{cleveref}
% defining theorem environments for their specialized versions
\makeatletter
\newenvironment{base@thm}[3]%
{$\;$\linebreak\noindent\mbox{$\triangleright\;\textsc{\textbf{\large #1} #2 (#3).}$}}
{\hfill$\;$\linebreak}
\makeatother
% lemma environments take a label and a name
\newcounter{lemma}
\crefname{lemma}{lemma}{lemmata}
\makeatletter
\newenvironment{lemma}[2]%
{\refstepcounter{lemma}\begin{base@thm}{Lemma}{\thelemma}{#2}\label[lemma]{lemma:#1}\def\@currentlabel{#2}\label{t@lemma:#1}}
{\end{base@thm}\noindent}
\makeatother
% referencing a lemma
\newcommand{\lemmaref}[1]{\Cref{lemma:#1}~(\ref{t@lemma:#1})}
\newcommand{\lemref}[1]{\Cref{lemma:#1}}
%%%% CHANGES BELOW %%%%
% 1. Removed passumptslist
% 2. Removed your crefaliases
% The crefname are kept
\crefname{asm}{assumption}{assumptions}
\crefname{goal}{goal}{goals}
% 3. Defined assumptions, passumptions, and goals directly as new lists
\newlist{assumptions}{enumerate}{1}
\newlist{passumptions}{enumerate}{1}
\newlist{goals}{enumerate}{1}
% 3. (cont'd) and set their formatting directly
\setlist[assumptions]{label=(\alph*)}
\setlist[goals]{label=(\roman*)}
% 4. Set the format for passumptions; define macro to change the label.
\makeatletter
\newcommand*\setPassLabel[1]{\gdef\@passLabel{#1}}
\setlist[passumptions]{resume,label={$(\@passLabel_{\arabic*})$}}
\makeatother
%%%%% BELOW AGAIN UNCHANGED, EXCEPT MARKED LINES %%%%%%%
\newcommand{\IH}[1][]{I{\kern-1.5pt}H#1}
% individual cases
\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {$#2$}}
\newcommand{\goal}[2]{\item\label[goal]{goal:#1} {$#2$}}
\newcommand{\asmref}[1]{\Cref{asm:#1}}
\newcommand{\goalref}[1]{\Cref{goal:#1}}
%%%% Make the list that is restarted the passumptions list, and not passumptionslist.
\newenvironment{proof}[1][\textbf{Proof}]%
{\restartlist{passumptions}\par\noindent\ignorespaces\mbox{\textsc{#1}.}$\;$\\\noindent}
{$\;$\hfill$\square$\\\ignorespacesafterend}
\begin{document}
\begin{lemma}{lemma}{Some Lemma Name}
If
\begin{assumptions}
\asm{lemma}{a}
\end{assumptions}
then
\begin{goals}
\goal{lemma}{b}
\end{goals}
\end{lemma}
\begin{proof}
\setPassLabel{H} %%% Set the label for the passumptions.
Unfold \goalref{lemma} and introduce the assumptions.
\begin{passumptions} %%% No more mandatory argument (Ditto below)
\asm{lemma0}{v\Downarrow b}
\end{passumptions}
What is left to show is:
\begin{goals}
\goal{lemma0}{c}
\end{goals}
From \asmref{lemma0}, get:
\begin{passumptions}
\asm{lemma1}{a=v}
\end{passumptions}
Rewrite \asmref{lemma} and \asmref{lemma0} using \asmref{lemma1}:
\begin{passumptions}
\asm{lemmap}{v=c}
\asm{lemma0p}{a\Downarrow b}
\end{passumptions}
Use \Cref{asm:lemma0p}; \asmref{lemma0p}:
\begin{passumptions}
\asm{lemma2}{a=b}
\end{passumptions}
Finally, rewrite \goalref{lemma0} via \asmref{lemma1}.
Then, \asmref{lemma2} solves it.
\end{proof}
\begin{proof}
\setPassLabel{K}
\begin{passumptions}
\asm{lemma3}{c}
\end{passumptions}
\end{proof}
\end{document}
Explicaciones
Creo que es mejor definir todos los entornos de su lista directamente como
\newlists
, en lugar de tenerlos como\newenvironments
envoltorios de la listapassumptionslist
. La razón es que de esta manera tiene la coherencia para usar laenumitem
interfaz para modificar las listas, ya sea sobre la marcha mediante argumentos opcionales o globalmente usando\setlist
, lo que aumenta la flexibilidad sobre cómo puede usarlas.Por lo que puedo decir,
\crefalias
no tiene ningún impacto en su código; así que los eliminé, especialmente porque se sabe que sonhyperref
potencialmente problemáticos. Lo que hace que esto sea bueno es que está usando el argumento opcional\label
siempre que lo use\asm
y\goal
de todos modos, por lo que\Cref
podrá recoger la información de tipo del argumento opcional.Creo que usar
\newlists
y\setlist
hace que la configuración del formato sea más transparente.Esta es la única parte en la que tenemos que trabajar. En su código original, definió
passumptions
con un argumento obligatorio que usa para configurarlabel
la lista subyacente. No estoy de acuerdo con esta configuración: en primer lugar, según su código, es evidente quepassumptions
se usa "por prueba", por lo que lo ideal es que las consecutivaspassumptions
en la misma prueba tengan el mismo tipo de etiqueta. (Creo que se vería extraño si los elementos estuvieran numerados H1, H2 y luego C3, D4, H5). Con eso en mente, es mejor tener una manera de cambiar la etiqueta globalmente (para todaspassumptions
las listas siguientes). Por cierto, hacerlo también permite codificarpassumptions
como\newlist
, ya que las listas creadas de esta manera no pueden tomar un argumento obligatorio.Con esta filosofía de diseño, creamos un comando para establecer el símbolo/carácter de etiquetado
passumptions
y usamos ese símbolo para configurar el formato de esta lista. También agregué laresume
clave ya que esta lista debe reanudarse, hasta que se restablezca al comienzo de cada prueba.