Diferencias semánticas de definiciones globales en grupos locales relacionadas con entradas de pila guardada

Diferencias semánticas de definiciones globales en grupos locales relacionadas con entradas de pila guardada

Esta es una pregunta de seguimiento sobre\variante global de \csname…\endcsname, que pide una solución al problema allí explicado, pero no pide una explicación.

La esencia del problema es que

{ \gdef\foo{...} }

parece comportarse de manera diferente a

{ \expandafter\gdef\csname foo\endcsname{...} }

donde este último "agrega una entrada de retención en [el] save_stack".

Entonces me gustaría saber:

  1. ¿Por qué hay una diferencia en el comportamiento de esos dos constructos? ¿Es esto solo un efecto secundario de la forma en que \csname...\endcsnamese implementa, o es una diferencia semántica intencional (hasta donde es posible saberlo)?
  2. ¿Qué significa exactamente la {retaining ...}entrada en el archivo de registro mencionada en la pregunta vinculada? ¿Por qué sería necesario mantener las definiciones locales una vez cerrado el grupo?

Respuesta1

Gracias por hacer esta pregunta. No entendí muy bien el ejemplo enla otra preguntacuando lo leí, pero esta pregunta me motivó a mirarlo nuevamente y creo que ahora lo entiendo.

Básicamente, lo que no quedó claro en el ejemplo es que el problema se aplica cuando tenemos varios\expandafter\gdef\csname foo\endcsname{...} construcciones de este tipo dentro del mismo.mismogrupo (o grupos anidados dentro de ese grupo), es decir, la pila guardada crece solo mientras está dentro del grupo. El mensaje “reteniendo…” se imprime al momento de salir del grupo (cuando se está sacando la pila), pero este mensaje no es en sí mismo un problema; sólo sirve como evidencia de que la pila había crecido antes.

Ese párrafo probablemente fue confuso, así que comprendamos la pila de guardado desde cero. :-)


1.Considere este ejemplo:

\def\a{hello}
{
    \def\a{world}
}

Aquí, cuando TeX en el grupo interno ve \aque se redefine (a world), guarda el valor anterior (una referencia a una lista de tokens que contiene hello) en la pila de guardado. Luego, cuando llega al final del grupo, abre la pila para restaurar la definición de hello. Esta es la razón obvia para guardar la pila, y el siguiente es el resultado de seguimiento correspondiente, asumiendo que tiene \tracingrestores=2y \tracinggroups=2y también \tracingassigns=2y que el grupo interno comienza en la línea 10 (por ejemplo). También modifiqué el resultado para eliminar el salto de línea. antes {into…}:

{changing \a=undefined}{into \a=macro:->hello}
{entering simple group (level 1) at line 10}
{changing \a=macro:->hello}{into \a=macro:->world}
{restoring \a=macro:->hello}
{leaving simple group (level 1) entered at line 10}

2.Ahora considere el mismo ejemplo,sinel \def\a{hello}de arriba:

{
    \def\a{world}
}

— una vez más, cuando TeX ve \def\a{world}dentro del grupo, tiene que guardar el significado anterior de \a. Resultó que no estaba definido, pero aún necesitamos que lo esté nuevamente después de abandonar el grupo, por lo que TeX necesita poner el significado "indefinido" en la pila de guardado. El resultado del seguimiento es:

{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=macro:->world}
{restoring \a=undefined}
{leaving simple group (level 1) entered at line 10}

3.Consideremos ahora un ejemplo similar:

{
  \let\a=\relax
  \gdef\a{world}
}

Aquí, cuando TeX ve el primero \let\a=\relax, tiene que guardar el significado anterior (“indefinido”) en la pila de guardado, tal como en el ejemplo anterior. Luego, cuando vea el mensaje, \gdefno tendrá que poner nada en la pila de guardado. Finalmente, cuando llega al final del grupo y comienza a extraer su pila (que actualmente contiene el significado "indefinido" \a), observa que \aahora tiene una definición global, por lo que ignora el significado "indefinido" y conserva la definición global. Esto explica el resultado del seguimiento:

{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=\relax}
{globally changing \a=\relax}{into \a=macro:->world}
{retaining \a=macro:->world}
{leaving simple group (level 1) entered at line 10}

4.Finalmente, considere este ejemplo:

{
  \expandafter\gdef\csname a\endcsname{world}
}

Esto resulta ser exactamente igual que el caso anterior. Esto se debe a que cuando TeX ve el, \expandafterpasa temporalmente sobre el \gdefy comienza a actuar en el siguiente token ( \csname); esto significa actuar \csname a\endcsnamepara crear la macro \a, con una definición de \relaxsi aún no estaba definida (así es como \csnamefunciona), y sólo después de eso actúa sobre el (anteriormente pasado por alto temporalmente) \gdefy redefine \aa la nueva definición que sigue ( world). Por lo tanto, el resultado del rastreo es el mismo que la vez anterior:

{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=\relax}
{globally changing \a=\relax}{into \a=macro:->world}
{retaining \a=macro:->world}
{leaving simple group (level 1) entered at line 10}

Eso es todo al respecto. Para responder a sus preguntas específicas:

  • (1) Es sólo un efecto secundario de la forma en que \csname … \endcsnamese implementa; es decir, \letes la señal para \relaxllegar primero. Sin embargo, esto está bien documentado y, por lo tanto, podría decirse que también forma parte de la semántica (como todos esperan).

  • (2a) La {retaining ...}entrada en el archivo de registro significa (consulte la página 301 deEl libro de texto) que elmás tempranoLa definición colocada en la pila de guardado, es decir, cualquiera que fuera el valor de la macro antes de que fuera establecida \relaxpor \csname … \endcsname, se ha ignorado debido a \gdef(y se ha conservado la definición global).

  • (2b) “¿Por qué sería necesario mantener las definiciones locales una vez cerrado el grupo?” — No lo es, y no se conservan. En cambio, lo que ves es quecuandoCuando se cierra el grupo, ahora se examinan todas las definiciones que se guardaron anteriormente debido a definiciones locales realizadas dentro del mismo grupo y, si ha habido una definición global en algún momento, ahora se descartan. Al final del grupo, la pila de guardado estará vacía (o más bien, tendrá el mismo tamaño que cuando se ingresó al grupo).


Más específicamente, aquí hay un ejemplo del problema y las soluciones enesa pregunta. El autor de la pregunta estaba definiendo muchas macros (esencialmente) dentro de un grupo, de una manera más o menos equivalente a:

{
    \expandafter\gdef\csname A\endcsname{I'm A}
    \expandafter\gdef\csname B\endcsname{I'm B}
    \expandafter\gdef\csname C\endcsname{I'm C}
}

etcétera. Como vimos en los ejemplos 3 y 4 anteriores, esto equivale a:

{
    \let\A=\relax \gdef\A{I'm A}
    \let\B=\relax \gdef\B{I'm B}
    \let\C=\relax \gdef\C{I'm C}
}

etcétera. Por lo tanto, cada definición anterior coloca una entrada en la pila de guardado (por el significado antes de que el nombre de la secuencia de control fuera \letpor \relax) \csname ... \endcsname, y solo al final del grupo aparecen todas estas entradas. Entonces, si hay demasiadas definiciones de este tipo; te quedarás sin “tamaño guardado”.

Elprimera respuesta publicada(por Steven B. Segletes) sugirió hacer el equivalente a \csname … \endcsnameejecutar cada uno en el nivel más alto (donde no se colocará nada en la pila de guardado).

Elsegunda respuesta publicada(por Marcel Krüger) sugirió hacer el equivalente a:

{
  \begingroup\expandafter\endgroup\expandafter\gdef\csname A\endcsname{I'm A}
  \begingroup\expandafter\endgroup\expandafter\gdef\csname B\endcsname{I'm B}
  \begingroup\expandafter\endgroup\expandafter\gdef\csname C\endcsname{I'm C}
}

donde las definiciones ocurren dentro de grupos de los que se sale inmediatamente, por lo que cada pila se abre inmediatamente: la salida de seguimiento es (saltos de línea de módulo):

{entering simple group (level 1) at line 10}

{entering semi simple group (level 2) at line 11}
{changing \A=undefined}{into \A=\relax}
{restoring \A=undefined}
{leaving semi simple group (level 2) entered at line 11}
{globally changing \A=undefined}{into \A=macro:->I'm A}

{entering semi simple group (level 2) at line 12}
{changing \B=undefined}{into \B=\relax}
{restoring \B=undefined}
{leaving semi simple group (level 2) entered at line 12}
{globally changing \B=undefined}{into \B=macro:->I'm B}

{entering semi simple group (level 2) at line 13}
{changing \C=undefined}{into \C=\relax}
{restoring \C=undefined}
{leaving semi simple group (level 2) entered at line 13}
{globally changing \C=undefined}{into \C=macro:->I'm C}

{leaving simple group (level 1) entered at line 10}

Si no le importan las opciones expandibles, etc. y solo está tratando de comprender la pila de guardado, entonces la solución propuesta es la siguiente:

{
    {\let\A=\relax} \gdef\A{I'm A}
    {\let\B=\relax} \gdef\B{I'm B}
    {\let\C=\relax} \gdef\C{I'm C}
}

para que puedas ver por qué la pila guardada no sigue creciendo.

información relacionada