Hacer referencia a elementos en la lista de currículum* muestra texto adicional

Hacer referencia a elementos en la lista de currículum* muestra texto adicional

Quería automatizar un poco mi vida y descubrí que el enumitempaquete 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 enumicontador \setcountery 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?

imagen que representa el problema

\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 enumitemcó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, enumerateo passumptionslist) en (esencialmente) lo que se pasó como argumento opcional a la enumitemlista.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 ... \endcsnameconstrucció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 ... \endcsnamepara 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 #2está 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 \relaxes 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 passumptionslisty lo has llamadoantesllamando al primero passumptionsen 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, enumitemse comporta de manera diferente en cuatro casos diferentes.

  1. Cuando tiene una seriesopción que no es a resumeo a resume*, el código guarda tanto el valor del contador actual como los argumentos opcionales actuales usando \global\def.
  2. Cuando tiene una seriesopción que también es a resumeo a resume*, el código guarda solo el contador a través de \global\def, pero no guarda los argumentos opcionales actuales.
  3. Cuando no tiene un series, pero tiene un resume*, el código guarda solo el contador a través de \global\def, pero no guarda los argumentos opcionales.
  4. Cuando tienes una lista simple que no es ni a seriesni a resume*(pero, curiosamente, resumeaquí 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, \defno 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 el assumptionsentorno 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 enumitemno 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 resumelocal 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 passumptionslistque se incluye en varios entornos es innecesario. Es mejor definir todo como \newlists.

%%%% 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}

ingrese la descripción de la imagen aquí

Explicaciones

  1. Creo que es mejor definir todos los entornos de su lista directamente como \newlists, en lugar de tenerlos como \newenvironmentsenvoltorios de la lista passumptionslist. La razón es que de esta manera tiene la coherencia para usar la enumiteminterfaz 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.

  2. Por lo que puedo decir, \crefaliasno tiene ningún impacto en su código; así que los eliminé, especialmente porque se sabe que son hyperrefpotencialmente problemáticos. Lo que hace que esto sea bueno es que está usando el argumento opcional \labelsiempre que lo use \asmy \goalde todos modos, por lo que \Crefpodrá recoger la información de tipo del argumento opcional.

  3. Creo que usar \newlistsy \setlisthace que la configuración del formato sea más transparente.

  4. Esta es la única parte en la que tenemos que trabajar. En su código original, definió passumptionscon un argumento obligatorio que usa para configurar labella lista subyacente. No estoy de acuerdo con esta configuración: en primer lugar, según su código, es evidente que passumptionsse usa "por prueba", por lo que lo ideal es que las consecutivas passumptionsen 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 todas passumptionslas listas siguientes). Por cierto, hacerlo también permite codificar passumptionscomo \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 passumptionsy usamos ese símbolo para configurar el formato de esta lista. También agregué la resumeclave ya que esta lista debe reanudarse, hasta que se restablezca al comienzo de cada prueba.

información relacionada