How to create a sub-function that modify a local variable from the outer function?

I recently needed to factorize a snippet that was modifying a local variable in a function. To do so, I tried to write a sub-function, hopping that my local variable would be captured mutably.

let foo() = {
  let x = 1

  // The block of code I’m trying to factorise
  let bar() = {
    x += 1 // something more complex in reality
    // The line above generate this error:
    // Variables from outside the function are read-only and cannot be modified
  }

  if some-condition {
    bar()
    // other stuff
  } else if some-other-condition {
    // more stuff
    bar()
  } else {
    // …
  }
}

Is there a way to write lambda in typst?

So far the best I found is to use state, but it feels very heavyweight.

let foo() = {
  let x = state("why is this needed", 1)
  
  let bar() = {
    x.update(x => x+1)
  }
  
  bar()
  bar()
  context x.get() // prints 3 as expected
}

User functions in Typst are pure and do not modify state outside of their body. The arguments are always passed by value.

image

So state is a powerful feature that allows modifying stuff outside as long as you include any state.updates in the document (and read the state value with context after the said updates).

Should I read your answer as “lambda do not exists in typst, if I want to factorize the modification of a local variable, I need to either use the heavy weight state type or copy-paste the code”?

Typst has unnamed functions which are also user defined, therefore pure. The rest it true. You can also just not use abstractions and use inline code, which will be the most performant in theory. Though if a function will sometimes return the same values and does a lot of work, then cached functions might be better.

This isn’t nice in term of reviewing/maintainability, but obviously not blocking. The exact context in which I needed it was in another question I recently asked

let wrapp-section(
  ...
) = {
  let fn = none
  let fenced = ()

  for it in body.children {
    if (is-heading(it) and it.fields().at("depth") < depth) {
      if fn != none {
        // Complete the last fence
        fn(fenced.join())
        fn = none
        fenced = ()
      }
      // some code…
    } else if is-heading(it) and it.fields().at("depth") == depth {
      if fn != none {
        // Complete the last fence
        fn(fenced.join())
        fn = none
        fenced = ()
      }
      // some other code…
    } else if fn != none {
      // ...
    } else {
      // ...
    }
  }

  // ...
}

As you can see, the following block is repeated two times, and I wanted to create what would be a macro/lambda in other languages to factorize it:

if fn != none {
  // Complete the last fence
  fn(fenced.join())
  fn = none
  fenced = ()
}

I don’t know why would you bother to abstract such a small block of code that is only repeated 2 times anyway.

Since you output some stuff, you can’t make it very clean.

#let wrapp-section(/* ... */) = {
  let fn = none
  let fenced = ()

  let sub-function(fn, fenced) = {
    let output
    if fn != none {
      output = fn(fenced.join())
      fn = none
      fenced = ()
    }
    (fn, fenced, output)
  }

  let output
  for it in body.children {
    if is-heading(it) and it.fields().at("depth") < depth {
      (fn, fenced, output) = sub-function(fn, fenced)
      output
      // some code…
    } else if is-heading(it) and it.fields().at("depth") == depth {
      (fn, fenced, output) = sub-function(fn, fenced)
      output
      // some other code…
    } else if fn != none {
      /* ... */
    } else {
      /* ... */
    }
  }

  /* ... */
}
1 Like

Hello,

I don’t know why would you bother to abstract such a small block of code that is only repeated 2 times anyway.

That’s just an habit from other programming languages, since function can be named and named things helps to show intention. I was also asking this question to improve my understanding of why tools I can use in typst.

So thanks for the answers. I wish typst had local non-escaping lambda or macro, but I can live without.

1 Like