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()]