mirror of
https://github.com/typst/typst
synced 2025-05-12 03:56:21 +08:00
Documentation for state
This commit is contained in:
parent
188e64fa30
commit
c8bf331c94
@ -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
|
||||
|
@ -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 `<here>` is
|
||||
/// #locate(loc => s.at(
|
||||
/// query(<here>, loc)
|
||||
/// .first()
|
||||
/// .location()
|
||||
/// ))
|
||||
///
|
||||
/// #compute("10")
|
||||
/// #compute("x + 3")
|
||||
/// *Here.* <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
|
||||
|
Loading…
x
Reference in New Issue
Block a user