States

This section is outdated. It may be still useful, but it is strongly recommended to study new context system (using the reference).

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
Rendered image

Context magic

So what does this magic context s.get() mean?

Context in Reference

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())}
Rendered image

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!
Rendered image

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())
}
Rendered image

ID collision

TLDR; Never allow colliding states.

States are described by their id-s, if they are the same, the code will break.

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))
Rendered image

However, this may seem okay:

// locations in code are different!
#let x = state("state-id")
#let y = state("state-id", 2)

#x, #y
Rendered image

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