How to share settings between template functions?

Hi all,

I’m writing a simple slides template for my own usage. A couple of settings are different from project to project and are shared by some functions of the template. Let’s say a shared setting is bg, used by functions f1 and f2. AFAICS there are two approaches to achieve what I need:

  1. A factory function in the template that creates closures for f1 and f2 with the project-specific value for bg. In this case bg is inmutable, but I’m ok with that.

  2. Having a state object for bg in the template, as suggested in Write once variables · Issue #1913 · typst/typst · GitHub.

I’d like to implement the second alternative but I’m having some trouble getting the state:

#let config = (bg: state("slides.bg"))

#let slides(body, bg) = {
    config.S.update(bg)
    body
}

#let f1() = {
    box(fill: config.bg.get(), "hello")
}

#let f2() = {
    box(fill: config.bg.get(), "world")
}

At this point Typst complained about missing context in the get expressions. Fine, I replaced them by:

box(fill: context config.bg.get(), ...)

but then I got expected color, gradient, tiling, or none, found content. Ok, I assume context always build content, disregarding the type of the contextualized expression. I changed the expressions again to:

context box(fill: config.bg.get(), ...)

This works for this simple example, but in my real code I would like to assign the setting to a variable in order to use it in a number of expressions:

#let f1() = {
    let bg = context config.bg.get()
    box(fill: bg, "hello")
}

but now I’m obviously back at the expected something, found content error. Maybe:

#let f1() = {
    context let bg = config.bg.get()
    box(fill: bg, "hello")
}

? Nope: unknown variable bg, the scoping is wrong. Let’s try another one:

#let f1() = context {
    let bg = config.bg.get()
    box(fill: bg, "hello")
}

Ok, this works. But I’m not really comfortable with contextualizing the entire block just because of bg, at least because it makes difficult to spot where context is actually needed (not in this simplistic example, of course).

Do you have any better ideas? Some suggestion on how to structure the code for the described use case?

Thanks!

Maybe I’m thinking about it the wrong way. Maybe I should consider as context -dependant every usage of bg and not only its definition. In that case, the narrowest contextualized scope (without querying the state multiple times) may be something like:

#let f1() = {
  context {
    let bg = config.bg.get()
    box(fill: bg, "hello")
    ...
    box(fill: bg, "hello")
  }
  ...
}

at which point I may just prefer:

#let f1() = context {
  let bg = config.bg.get()
  box(fill: bg, "hello")
  ...
  box(fill: bg, "hello")
  ...
}

Hi, welcome to the Typst forum! From your second message it looks like you figured out how to used context.

For this use case my preferred approach currently is to have a config() function that returns functions with pre-applied arguments:

#let slide(config, body) = page(fill: config.bg, body)

#let f1(config) = {
    box(fill: config.bg, "hello")
}

#let f2(config) = {
    box(fill: config.bg, "world")
}

#let config(..args) = {
  let cfg = args.named()
  return (
    slide: slide.with(cfg),
    f1: f1.with(cfg),
    f2: f2.with(cfg),
  )
}

// Usage
#let (slide, f1, f2) = config(bg: blue)

#slide[Body]
#f1()
#f2()

(There is a lot of discussion among developers about how to make these things easier in the future, e.g. with custom types or dynamic modules.)

Hi. Sounds like something similar to How can I have global configuration parameters for a module/package?. Basically, there are several approaches, and each is better for different use cases, which there are a ton of.