States
Before we start something practical, it is important to understand states in general.
Here is a good explanation of why do we need them: Official Reference about states. It is highly recommended to read it first.
So instead of
#let x = 0
#let compute(expr) = {
// eval evaluates string as Typst code
// to calculate new x value
x = eval(
expr.replace("x", str(x))
)
[New value is #x.]
}
#compute("10") \
#compute("x + 3") \
#compute("x * 2") \
#compute("x - 5")
THIS DOES NOT COMPILE: Variables from outside the function are read-only and cannot be modified
Instead, you should write
#let s = state("x", 0)
#let compute(expr) = [
// updates x current state with this function
#s.update(x =>
eval(expr.replace("x", str(x)))
)
// and displays it
New value is #context s.get().
]
#compute("10") \
#compute("x + 3") \
#compute("x * 2") \
#compute("x - 5")
The computations will be made _in order_ they are _located_ in the document. So if you create computations first, but put them in the document later... See yourself:
#let more = [
#compute("x * 2") \
#compute("x - 5")
]
#compute("10") \
#compute("x + 3") \
#more
Context magic
So what does this magic context s.get() mean?
In short, it specifies what part of your code (or markup) can depend on states outside. This context-expression is packed then as one object, and it is evaluated on layout stage.
That means it is impossible to look from "normal" code at whatever is inside the context. This is a black box that would be known only after putting it into the document.
We will discuss context features later.
Operations with states
Creating new state
#let x = state("state-id")
#let y = state("state-id", 2)
#x, #y
State is #context x.get() \ // the same as
#context [State is #y.get()] \ // the same as
#context {"State is" + str(y.get())}
Update
Updating is a content that is an instruction. That instruction tells compiler that in this place of document the state should be updated.
#let x = state("x", 0)
#context x.get() \
#let _ = x.update(3)
// nothing happens, we don't put `update` into the document flow
#context x.get()
#repr(x.update(3)) // this is how that content looks \
#context x.update(3)
#context x.get() // Finally!
Here we can see one of important context traits: it "sees" states from outside, but can't see how they change inside it:
#let x = state("x", 0)
#context {
x.update(3)
str(x.get())
}
ID collision
TLDR; Never allow colliding states.
So, if you write functions or loops that are used several times, be careful!
#let f(x) = {
// return new state…
// …but their id-s are the same!
// so it will always be the same state!
let y = state("x", 0)
y.update(y => y + x)
context y.get()
}
#let a = f(2)
#let b = f(3)
#a, #b \
#raw(repr(a) + "\n" + repr(b))
However, this may seem okay:
// locations in code are different!
#let x = state("state-id")
#let y = state("state-id", 2)
#x, #y
But in fact, it isn't:
#let x = state("state-id")
#let y = state("state-id", 2)
#context [#x.get(); #y.get()]
#x.update(3)
#context [#x.get(); #y.get()]