diff --git a/library/src/meta/counter.rs b/library/src/meta/counter.rs index e5f03e539..9d00bf109 100644 --- a/library/src/meta/counter.rs +++ b/library/src/meta/counter.rs @@ -165,6 +165,11 @@ use crate::prelude::*; /// which one doesn't matter. After the heading follow two calls to `step()`, /// so the final value is `{(6,)}`. /// +/// ## Different kinds of state +/// The `counter` function is closely related to [state]($func/state) function. +/// Read its documentation for more details on state management in Typst and +/// why it doesn't just use normal variables for counters. +/// /// ## Methods /// ### display() /// Display the value of the counter. @@ -195,7 +200,7 @@ use crate::prelude::*; /// ### update() /// Update the value of the counter. /// -/// Just like `step()`, the update only occurs if you put the resulting +/// Just like with `step`, the update only occurs if you put the resulting /// content into the document. /// /// - value: integer or array or function (positional, required) @@ -223,7 +228,7 @@ use crate::prelude::*; /// /// - location: location (positional, required) /// Can be any location. Why is it required then? Typst has to evaluate parts -/// of your code multiple times to find out all counter's values. By only +/// of your code multiple times to determine all counter values. By only /// allowing this method in [`locate`]($func/locate) calls, the amount of code /// that can depend on the method's result is reduced. If you could call /// `final` directly at the top level of a module, the evaluation of the whole diff --git a/library/src/meta/state.rs b/library/src/meta/state.rs index 090f1ccfd..efea45aa7 100644 --- a/library/src/meta/state.rs +++ b/library/src/meta/state.rs @@ -5,7 +5,231 @@ use typst::eval::Tracer; use crate::prelude::*; -/// Handle stateful tasks. +/// Manage stateful parts of your document. +/// +/// Let's say you have some computations in your document and want to remember +/// the result of your last computation to use it in the next one. You might try +/// something similar the code below and would expect it to output 10, 13, 26, +/// and 21. However this **does not work** in Typst. If you test this code, you +/// will see that Typst complains with the following error message: _Variables +/// from outside the function are read-only and cannot be modified._ +/// +/// ```typ +/// #let x = 0 +/// #let compute(expr) = { +/// x = eval( +/// expr.replace("x", str(x)) +/// ) +/// [New value is #x. \ ] +/// } +/// +/// #compute("10") +/// #compute("x + 3") +/// #compute("x * 2") +/// #compute("x - 5") +/// ``` +/// +/// ## State and document markup +/// Why does it do that? Because, in general, this kind of computation with side +/// effects is problematic in document markup and Typst is upfront about that. +/// For the results to make sense, the computation must proceed in the same +/// order in which the results will be laid out in the document. In our simple +/// example, that's the case, but in general it might not be. +/// +/// Let's look at a slightly different, but similar kind of state: The heading +/// numbering. We want to increase the heading counter at each heading. Easy +/// enough, right? Just add one. Well, it's not that simple. Consider the +/// following example: +/// +/// ```example +/// #set heading(numbering: "1.") +/// #let template(body) = [ +/// = Outline +/// ... +/// #body +/// ] +/// +/// #show: template +/// +/// = Introduction +/// ... +/// ``` +/// +/// Here, Typst first processes the body of the document after the show rule, +/// sees the `Introduction` heading, then passes the resulting content to the +/// `template` function and only then sees the `Outline`. Just counting up would +/// number the `Introduction` with `1` and the `Outline` with `2`. +/// +/// ## Managing state in Typst +/// So what do we do instead? We use Typst's state management system. Calling +/// the `state` function with an identifying string key and an optional initial +/// value gives you a state value which exposes a few methods. The two most +/// important ones are `display` and `update`: +/// +/// - The `display` method shows the current value of the state. You can +/// optionally give it a function that receives the value and formats it in +/// some way. +/// +/// - The `update` method modifies the state. You can give it any value. If +/// given a non-function value, it sets the state to that value. If given a +/// function, that function receives the previous state and has to return the +/// new state. +/// +/// Our initial example would now look like this: +/// +/// ```example +/// #let s = state("x", 0) +/// #let compute(expr) = [ +/// #s.update(x => +/// eval(expr.replace("x", str(x))) +/// ) +/// New value is #s.display(). \ +/// ] +/// +/// #compute("10") +/// #compute("x + 3") +/// #compute("x * 2") +/// #compute("x - 5") +/// ``` +/// +/// State managed by Typst is always updated in layout order, not in evaluation +/// order. The `update` method returns content and its effect occurs at the +/// position where the returned content is inserted into the document. +/// +/// As a result, we can now also store some of the computations in +/// variables, but they still show the correct results: +/// +/// ```example +/// >>> #let s = state("x", 0) +/// >>> #let compute(expr) = [ +/// >>> #s.update(x => +/// >>> eval(expr.replace("x", str(x))) +/// >>> ) +/// >>> New value is #s.display(). \ +/// >>> ] +/// <<< ... +/// +/// #let more = [ +/// #compute("x * 2") +/// #compute("x - 5") +/// ] +/// +/// #compute("10") +/// #compute("x + 3") +/// #more +/// ``` +/// +/// This example is of course a bit silly, but in practice this is often exactly +/// what you want! A good example are heading counters, which is why Typst's +/// [counting system]($func/counter) is very similar to its state system. +/// +/// ## Time Travel +/// By using Typst's state management system you also get time travel +/// capabilities! By combining the state system with [`locate`]($func/locate) +/// and [`query`]($func/query), we can find out what the value of the state will +/// be at any position in the document from anywhere else. In particular, the +/// `at` method gives us the value of the state at any location and the `final` +/// methods gives us the value of the state at the end of the document. +/// +/// ```example +/// >>> #let s = state("x", 0) +/// >>> #let compute(expr) = [ +/// >>> #s.update(x => { +/// >>> eval(expr.replace("x", str(x))) +/// >>> }) +/// >>> New value is #s.display(). \ +/// >>> ] +/// <<< ... +/// +/// Value at `` is +/// #locate(loc => s.at( +/// query(, loc) +/// .first() +/// .location() +/// )) +/// +/// #compute("10") +/// #compute("x + 3") +/// *Here.* \ +/// #compute("x * 2") +/// #compute("x - 5") +/// ``` +/// +/// ## A word of caution +/// To resolve the values of all states, Typst evaluates parts of your code +/// multiple times. However, there is no guarantee that your state manipulation +/// can actually be completely resolved. +/// +/// For instance, if you generate state updates depending on the final value of +/// a state, the results might never converge. The example below illustrates +/// this. We initialize our state with `1` and then update it to its own final +/// value plus 1. So it should be `2`, but then its final value is `2`, so it +/// should be `3`, and so on. This example display `4` because Typst simply +/// gives up after a few attempts. +/// +/// ```example +/// #let s = state("x", 1) +/// #locate(loc => { +/// s.update(s.final(loc) + 1) +/// }) +/// #s.display() +/// ``` +/// +/// In general, you should _typically_ not generate state updates from within +/// `locate` calls or `display` calls of state or counters. Instead pass a +/// function to `update` that determines the value of the state based on its +/// previous value. +/// +/// ## Methods +/// ### display() +/// Display the value of the state. +/// +/// - format: function (positional) +/// A function which receives the value of the state and can return arbitrary +/// content which is then displayed. If this is omitted, the value is directly +/// displayed. +/// +/// - returns: content +/// +/// ### update() +/// Update the value of the state. +/// +/// The update will be in effect at the position where the returned content is +/// inserted into the document. If you don't put the output into the document, +/// nothing happens! This would be the case, for example, if you write +/// `{let _ = state("key").update(7)}`. State updates are always applied in +/// layout order and in that case, Typst wouldn't know when to update the state. +/// +/// - value: any or function (positional, required) +/// If given a non function-value, sets the state to that value. If given a +/// function, that function receives the previous state and has to return the +/// new state. +/// +/// - returns: content +/// +/// ### at() +/// Get the value of the state at the given location. +/// +/// - location: location (positional, required) +/// The location at which the state's value should be retrieved. A suitable +/// location can be retrieved from [`locate`]($func/locate) or +/// [`query`]($func/query). +/// +/// - returns: array +/// +/// ### final() +/// Get the value of the state at the end of the document. +/// +/// - location: location (positional, required) +/// Can be any location. Why is it required then? As noted before, Typst +/// has to evaluate parts of your code multiple times to determine the values +/// of all state. By only allowing this method in [`locate`]($func/locate) +/// calls, the amount of code that can depend on the method's result is +/// reduced. If you could call `final` directly at the top level of a module, +/// the evaluation of the whole module and its exports could depend on the +/// state's value. +/// +/// - returns: array /// /// Display: State /// Category: meta