How do I create unique identifiers and access them immediately?

Happy holidays, everyone!

I need a way to generate a globally unique identifier that I can access within the same layout iteration. My current approach looks like this:

#let id = state("refsection-id", "ref")

#context {
  id.update(x => x + "_1")
  // <-- here
}

// <-- or here

One could alternatively use a counter, I suppose. I don’t particularly care what the identifier looks like, as long as I can access it as a string or int and it is globally unique to the document. I store it in a state and access it throughout a specific region of a document.

Now my question is: Can we find any way in which I can access the updated value of the state/counter at either of the two locations marked with “here” in the code snippet - within the same layout iteration in which the value is updated?

Obviously I can do it with a get, but my understanding is that this get call will only return the correct value in the next layout iteration. My code already uses four layout iterations in the worst case, and I don’t think I can afford this. state.update and counter.step both return content, which doesn’t seem to contain the updated value even in its fields.

Any thoughts?

If I take the code at face value, the regular way to do this would be like this:

#let id = state("refsection-id", "ref")
#id.update(x => x + "_1")
#context {
  // <-- here
  let my-id = id.get()
}
  1. state.update does not need context
  2. context is acquired after the update so the get will see the update. Not in the first layout iteration, but the state value will be available there as soon as possible
    NOTE that in your example, at here, the get will never see the updated state value, because the context is acquired before the update happens, and this fact is not about layout iterations, just the rules of state.

If you must put the update into the same context block then I would do something like this:

#let id = state("refsection-id", "ref")

#context {
  let updater = x => x + "_1"
  id.update(updater)
  let my-id = updater(id.get())
}

As you know we can’t and should not store my-id into a state.update but we can do parallel mappings of the state value this way.

As a general note, I assume you want to use a counter, not state to count refsections. But the logic above is analogous when using counter.

Hi @bluss, thanks for the quick reply. I’m aware that I can access the value with get - my question was how to access it within the same layout iteration.

The call to get will only return the updated value in the next layout iteration, and I have other code depending on it in a contextual way. Thus, using get increases the total number of iterations until convergence by one.

typsy – Typst Universe?

#import "@preview/typsy:0.2.1": safe-counter

#let my-counter1 = safe-counter(()=>{})
#let my-counter2 = safe-counter(()=>{})
// ...these are different counters!

But for non-title question… There is no way to get the “updated” value without knowing previous or current state, i.e., you must call .get at some point. Either for previous value + compute the new one or for updated one. See example for both above.

One detail is unclear to me in the question, is there more to your example? In your example in the question, a get there would never return an updated value because the update happens after the most recent context. If this point is not clear then I think it’s important we straighten it out first.

Yeah, perhaps my MWE was too minimal. There is a lot more to the situation - this is in the context of Pergamon, which has relatively complex state handling. My internal notes on what happens in each layout iteration are here.

A call to get below the context in which the update will return the correct value in the next layout iteration. This is how the current code works. What exactly did you mean with “never return an updated value”?

However, as I said above, this will increase the number of iterations until convergence. My question was whether I can avoid this, e.g. by a variant of update that returns the updated value.

What I’m taking away from the comments is that there is no obvious solution. Does anyone have insights on why update and step return content and not the value, given that one can use get (in a later iteration) to retrieve the raw value?

Ah, that makes sense and that works because it’s another context block.

The original I thought implied something like this:

#let st1 = state("my-state", "initial")
#context {
  st1.update(x => x + "-updated")
  // here
  st1.get()
}

and here the st1.get() never sees anything other than "initial" for the reasons already stated - the context starts before the update.

In general - lacking knowledge of the bigger problem - I think that working with query and metadata (if needed) is often easier than using state. It might be possible to use query and metadata more extensively for your problem.

Your refsection function already wraps a section of the document doc; this is good for a document structure approach. You shouldn’t need state refsection-id to communicate between refsection and print-bibliography for example, because of this. That’s at least my hunch.

You could even contemplate storing data in the style chain (the way elembic stores its settings) if you want to explore another complicated option…

The reason why we they communicate through the refsection-id is that the refsection function effectively splits the document into sections, each of which collects its own references and can render its own bibliography. Thus, the code that prints the bibliography needs to know what refsection it’s in.

Currently I collect the references in each refsection in a state containing a dictionary, which I reset at the start of each refsection. The challenge that I’m facing is that if one cites a reference between where the bibliography is printed and the end of the refsection (e.g. in an appendix), those references don’t appear in the bibliography - I have to evaluate the state at the end of the refsection, not at the location of the bibliography.

I am intrigued by your suggestion to use metadata queries. Can you think of a way to query for the metadata elements that occur between two specific locations in the document (the start and end of the refsection)? Specifying a matching end location for the start location of the refsection is exactly the problem that motivated my original question.

The idea of storing metadata in a bibliography title as in alembic is pretty wild - I’ll have to think that through in more detail.

I have recently also looked at the iteration budget of Alexandria. fwiw, I don’t track what section a reference appears in, I just record them in a big list (but users have to use prefixes themselves to assign references to sections/bibliographies).

Iiuc, you generate the keys for section states with a counter, and that’s what drives iterations up? I think you can avoid that by putting everything in one state, even if you’re more structured than me:

#let references = state("references", ())

#let start-section() = references.update(references => {
  references.push(())
  references
})
#let add-reference(ref) = references.update(references => {
  references.last().push(ref)
  references
})

#start-section()
#add-reference("a")
#add-reference("b")

#start-section()
#add-reference("c")
#add-reference("d")

// (("a", "b"), ("c", "d"))
#context references.get()

In general, I find that using multiple states can make sharing information (such as IDs and counts) harder. If you use just one state, everything is available inside a single update(), without requiring get() at all. For example, you could make the state’s value a value of the shape

(
  refsection-counter: 0,
  refsections: (
    (
      id: "...",
      references: (..),
    ),
    ..
  ),
)`

To track even richer information.

You can do that by using before() and after(), that could be another approach for grouping references into sections. In the same context, first query all headings (or whatever delimits refsections), then use the locations of two adjacent markers to query the references.