It may seem an unexpected wish for a typesetting system, but sugar for monadic application. For example, suiji lets you create random numbers and you use it like this:
#import "@preview/suiji:0.4.0": *
#let random-point(rng) = {
let x; let y
(rng, x) = random-f(rng)
(rng, y) = random-f(rng)
(rng, (x, y))
}
#let rng = gen-rng-f(42)
#{
let p; let x
(rng, p) = random-point(rng)
(rng, x) = random-f(rng)
(p, x)
}
Note how each call to something using rng has to pass it in, store the returned changed rng, and process the actual return value. This comes from the immutability inherent to functional languages, but Monads are a solution for it.
Example in Haskell for the brave
Caution, very different syntax incoming:
randomPoint = do
x <- randomF
y <- randomF
return (x, y)
rng = genRngF(42)
main = generate rng (do
p <- randomPoint
x <- randomF
return (p, x))
(I hope that’s roughly correct…) The short of this is:
- in a
doblock, it’s implied that we manipulate a monadic value (here an rng). The value itself doesn’t even show up. - with
<-we modify the monadic value, while at the same time storing a result of the operation in a variable returnis a function, not a keyword, but it essentially terminates the sequence of modifications and specifies the result, which can in turn be extracted by<-- the
doblock as a whole wraps up the result, sorandomPointdoes not result in(x, y); it results in a monad that can operate on an rng to give(x, y) - at some point we therefore need to provide the actual rng and access the actual result; this is what the
generatefunction here (left as an exercise to the reader) does.
There are probably lots of monad-like structures in the Typst ecosystem (and I imagine custom types could lead to even more of them), but Suiji’s rng is probably one of the most obvious ones. Another obvious one is in my package stack-pointer:
#execute({
...
let (x, ..rest) = foo(); rest
...
})
I’m not actually sure what the sugar could look like; the two examples are also not exactly the same. Taking some inspiration from Rust’s ?, ! could indicate a “mutation”:
#let random-point(
// this function has a "mutable" parameter
rng!,
) = {
let x; let y
(rng, x) = random-f(rng)
(rng, y) = random-f(rng)
// when using the sugar, the array is unwrapped,
// and the `!` value is assigned to the parameter variable
(rng!, (x, y))
}
#let rng = gen-rng-f(42)
#{
(
// by suffixing the function call, we're opting to use the sugar
random-point(rng)!,
// note that we couldn't do
random-point(get-random())!,
// the mutable parameter needs to be assignable
// the same is possible with regular functions as well,
// so it's just sugar
{ let result; (rng, result) = random-point(rng); result },
)
}