Traducción no oficial al español rioplatense, generada con IA (Claude Sonnet 5.5) bajo la dirección y revisión de Jassiel Ovando. No es una publicación de Typst GmbH ni cuenta con su aval; ante cualquier duda, prevalece la documentación oficial en inglés. ¿Un error de traducción o una sugerencia? Reportalo. Tipografía: Reforma, de PampaType.

stateDesde: siempreIr al código fuente

Administra las partes con estado de tu documento.

Supongamos que tenés algunos cálculos en tu documento y querés recordar el resultado de tu último cálculo para usarlo en el siguiente. Podrías probar algo similar al código de abajo y esperar que muestre 10, 13, 26 y 21. Sin embargo, esto no funciona en Typst. Si probás este código, vas a ver que Typst se queja con el siguiente mensaje de error: Variables from outside the function are read-only and cannot be modified.

// This doesn't work!
#let star = 0
#let compute(expr) = {
  star = eval(
    expr.replace("⭐", str(star))
  )
  [New value is #star.]
}

#compute("10") \
#compute("⭐ + 3") \
#compute("⭐ * 2") \
#compute("⭐ - 5")

Estado y marcado del documento

¿Por qué hace eso? Porque, en general, este tipo de cálculo con efectos secundarios es problemático en el marcado de un documento y Typst es claro al respecto. Para que los resultados tengan sentido, el cálculo debe avanzar en el mismo orden en que se van a disponer los resultados en el documento. En nuestro ejemplo simple, ese es el caso, pero en general podría no serlo.

Veamos un tipo de estado ligeramente distinto, pero similar: la numeración de títulos. Queremos aumentar el contador de títulos en cada título. Bastante fácil, ¿no? Solo hay que sumar uno. Bueno, no es tan simple. Consideremos el siguiente ejemplo:

#set heading(numbering: "1.") #let template(body) = [ = Outline ... #body ] #show: template = Introduction ...

Acá, Typst primero procesa el cuerpo del documento después de la regla show, ve el título Introduction, luego le pasa el contenido resultante a la función template y recién entonces ve Outline. Simplemente contar hacia arriba numeraría Introduction con 1 y Outline con 2.

Gestionar estado en Typst

Entonces, ¿qué hacemos en cambio? Usamos el sistema de gestión de estado de Typst. Llamar a la función state con una clave de tipo cadena de texto que lo identifique y un valor inicial opcional te da un valor de estado que expone algunas funciones. Las dos más importantes son get y update:

Nuestro ejemplo inicial ahora se vería así:

#let star = state("star", 0) #let compute(expr) = { star.update(old => eval(expr.replace("⭐", str(old))) ) [New value is #context star.get().] } #compute("10") \ #compute("⭐ + 3") \ #compute("⭐ * 2") \ #compute("⭐ - 5")

El estado administrado por Typst siempre se actualiza en el orden del diseño, no en el orden de evaluación. El método update devuelve contenido y su efecto ocurre en la posición donde el contenido devuelto se inserta en el documento.

Como resultado, ahora también podemos guardar algunos de los cálculos en variables y aun así mostrar los resultados correctos:

... #let more = [ #compute("⭐ * 2") \ #compute("⭐ - 5") ] #compute("10") \ #compute("⭐ + 3") \ #more

Por supuesto, este ejemplo es un poco tonto, pero en la práctica ¡a menudo es exactamente lo que querés! Un buen ejemplo son los contadores de títulos, y por eso el sistema de conteo de Typst es muy similar a su sistema de estado.

Viaje en el tiempo

¡Al usar el sistema de gestión de estado de Typst también obtenés capacidades de viaje en el tiempo! Podemos averiguar cuál será el valor del estado en cualquier posición del documento desde cualquier otro lugar. En particular, el método at nos da el valor del estado en cualquier ubicación particular y el método final nos da el valor del estado al final del documento.

... Value at `<here>` is #context star.at(<here>) #compute("10") \ #compute("⭐ + 3") \ *Here.* <here> \ #compute("⭐ * 2") \ #compute("⭐ - 5")

Una advertencia

Para resolver los valores de todos los estados, Typst evalúa partes de tu código varias veces. Sin embargo, no hay garantía de que tu manipulación de estado se pueda resolver por completo.

Por ejemplo, si generás actualizaciones de estado que dependen del valor final de un estado, los resultados podrían no converger nunca. El siguiente ejemplo lo ilustra. Inicializamos nuestro estado con 1 y luego lo actualizamos a su propio valor final más 1. Entonces debería ser 2, pero entonces su valor final es 2, así que debería ser 3, y así sucesivamente. Este ejemplo muestra un valor finito porque Typst simplemente se rinde después de unos cuantos intentos.

// This is bad! #let x = state("key", 1) #context x.update(x.final() + 1) #context x.get()

En general, deberías intentar no generar actualizaciones de estado dentro de expresiones de contexto. Si es posible, intentá expresar tus actualizaciones como valores no contextuales o como funciones que calculan el valor nuevo a partir del valor anterior. A veces no hay otra opción, pero en esos casos te toca a vos asegurarte de que el resultado converja.

Constructor
Signo de pregunta
Ir al código fuente

Crea un estado nuevo identificado por una clave.

state(
str,any,
) → state

key
str
ObligatorioPosicional
Signo de pregunta

La clave que identifica este estado.

Cualquier actualización del estado se identificará con la clave de tipo cadena de texto. Si construís varios estados con la misma key, actualizar uno cualquiera afectará a todos.

init
any
Posicional
Signo de pregunta
Por defecto: none

El valor inicial del estado.

Si construís varios estados con la misma key pero distintos valores init, cada uno usará su propio valor inicial pero compartirán las actualizaciones. Específicamente, el valor de un estado en cierta ubicación del documento se calculará a partir del valor inicial de ese estado y de todas las actualizaciones anteriores de la clave del estado.

ExpandirVer ejemplo
#let banana = state("key", "🍌") #let broccoli = state("key", "🥦") #banana.update(it => it + "😋") #context [ - #state("key", "🍎").get() - #banana.get() - #broccoli.get() ]

Funciones
Signo de pregunta

getContextual
Signo de pregunta
Desde: 0.11.0Ir al código fuente

Obtiene el valor del estado en la ubicación actual.

Esto equivale a state.at(here()).

self.get() → any

atContextual
Signo de pregunta
Desde: siempreIr al código fuente

Obtiene el valor del estado en la coincidencia única del selector dado.

El selector debe coincidir exactamente con un elemento del documento. Los tipos de selectores más útiles para esto son las etiquetas y las ubicaciones.

selector
label o selector o location o function
ObligatorioPosicional
Signo de pregunta

El lugar en el que se debe obtener el valor del estado.

finalContextual
Signo de pregunta
Desde: siempreIr al código fuente

Obtiene el valor del estado al final del documento.

self.final() → any

updateDesde: siempreIr al código fuente

Actualiza el valor del estado.

Devuelve un fragmento invisible de contenido que hay que insertar en el documento para que tenga efecto. Este contenido invisible le indica a Typst que la actualización especificada debe ocurrir donde el contenido se inserte en el documento.

El estado es parte de tu documento y corre como un hilo incrustado en el contenido del documento. El valor de un estado es el resultado de todas las actualizaciones de estado que ocurrieron en el documento hasta ese punto.

Por eso state.update devuelve un fragmento invisible de contenido que tenés que devolver e incluir en el documento: una actualización de estado que no está “ubicada” en el documento no ocurre, y “cuándo” ocurre lo determina dónde la ubicás. Por eso también necesitás contexto para leer el estado: necesitás usar la posición actual del documento para saber en qué lugar del “hilo” del estado estás.

Guardar una actualización de estado en una variable (por ejemplo, let my-update = state("key").update(c => c * 2)) no tendrá ningún efecto por sí solo. Solo cuando insertás la variable #my-update en algún lugar del contenido del documento, la actualización tendrá efecto, en la posición donde se insertó. También podés usar #my-update varias veces en distintas posiciones. Entonces, la actualización también tendrá efecto varias veces.

A diferencia de get, at y final, esta función no requiere contexto. Esto se debe a que, para crear la actualización de estado, no necesitamos saber en qué parte del documento estamos. Solo necesitamos esa información para resolver el valor del estado.

self.update(functionany) → content

update
function o any
ObligatorioPosicional
Signo de pregunta

Un valor al que actualizar o una función con la que actualizar.

Al actualizar el estado a partir de su valor anterior, conviene preferir la forma con función en lugar de obtener el valor anterior del contexto. Esto permite que el compilador resuelva el estado final de manera eficiente y minimice la cantidad de iteraciones de diseño necesarias.

En el siguiente ejemplo, fill.update(f => not f) pintará los elementos impares de la lista con viñetas como se espera. Sin embargo, si se lo reemplaza por context fill.update(not fill.get()), el diseño no convergerá en 5 intentos, ya que cada actualización necesitará una iteración adicional para propagarse.

ExpandirVer ejemplo
#let fill = state("fill", false) #show list.item: it => { fill.update(f => not f) context { set text(fill: fuchsia) if fill.get() it } } #lorem(5).split().map(list.item).join()