So, the documentation for state.update mentions that update will return content, but it doesn’t return anything. It also mentions that an update will not be applied if the result is never shown, but it seems that all updates are applied sequentially. I’m confused.
Well, that’s the ingenious/tricky thing. state.update returns content, but what it returns is not visible. Think of it as an invisible token or cookie that represents the state update.
What Typst is trying to tell you is that state.update returns a sliver of content that you must include in the document’s content otherwise the state update won’t happen at all. Updates happen in the order and location that these “cookies” appear in the document…
I think you seem to have a handle on it. Maybe only that the final expected value should be 5 because before the final state you have done both +1 and +2 to the state, so the final value should be 2 + 1 + 2 = 5
OK, I understand now. The documentation really reads backwards for me. It made me feel as if it is necessary to follow any use of update() with a context(get()) to make the update stick.
IMHO, the documentation should emphasise that the function returns invisible content, like an anchor, to indicate where it should apply the update, and that you need to include this return value in the document. It should stress that just writing #state("statename").update(value) includes this invisible anchor in the document stream, which makes it do what you want in the vast mayority of the cases.
It may point out that you can go out of the way to make Typst forget about this anchor by using this special construct #let _ = state("statename").update(value) and never using _ anywhere. But it shouldn’t present that as a normal thing to do.
#let st = state("test_state", 0)
#let show_state(state) = [
State = #context(state.get())
]
Initial state: #show_state(st)
#st.update(1) // Invisible return gets inserted into document
#st.update(2) // This update overwrites the previous one
After update, expect 2: #show_state(st)
#st.update(c => c + 1)
#st.update(c => c + 2)
Final state, expect 5: #show_state(st)
#let _ = st.update(c => c + 1) // We throw away this invisible return
#st.update(c => c + 2)
Really final state, expect 7: #show_state(st)
State updates are always applied in layout order and in that case, Typst wouldn’t know when to update the state.
Well, the new docs already have an example that doesn’t print the updated value, though it still retrieves it, to show the new styling. An example that updates state and doesn’t show its effect is not a visually good representation of what’s going on, IMO.
Thank you very much for the documentation change. I was looking around but couldn’t find the reference in typst/doc/ . However, I do feel that your update still puts too much emphasis on the unusual case.
I tried my hand at explaining it this way:
self.update (any function) -> content
Set the value of the state to any or update the value of the state according to the function. function receives the old value of the state. It must return the new value of the state.
Example:
#let st = state("test_state", 0)
#let double(i) = {i * 2}
The initial value of our state is: #context(st.get()). // 0
#st.update(7)
The value of the state is now: #context(st.get()). // 7
#st.update(double)
And now, the value of the state is: #context(st.get()). // 14
Note that update returns an invisible marker that normally gets inserted straight into the document, as in the example above. This invisible marker tells Typst that, at this location, it should perform this update. That’ s because Typst apllies all updates in the order they appear in the final document (layout order), not necessary the source order. See above. (reference to the beginning of the state reference documentation)
If you want to, you can save this invisible marker for later use. This means that the update will not yet be applied. Continuuing the example:
#let doubler = st.update(double) // not inserted yet
The value is unchanged, despite the update: #context(st.get()). // 14
#doubler // insert the invisible marker
But now, the update has been applied. The state is: #context(st.get()). // 28
#doubler // insert the invisible marker again
We can apply the same update twice! State is now: #context(st.get()). // 56