How to typeset juxtaposed function application in math mode?

There are two common ways to express function application:

  • arguments in parentheses f(x, y, z), which is currently supported well in Typst
  • juxtaposed style f x y z, common in functional programming, lambda calculus, and logic, but not supported very well in Typst

Today, juxtaposed variables (those written one after another, separated by spaces) will always be pushed together without any space between them:

#let bdot = math.class("relation", ".")
$(exists x bdot f x (g x) = (lambda y bdot h y) x)$

This causes a lot of trouble for domains where the juxtaposed syntax is common; in my case, this affects writing lambda calculus expressions. The current workaround is to manually add spaces just about everywhere:

#let bdot = math.class("relation", ".")
$(exists x bdot f" "x" "(g" "x) = (lambda y bdot h" "y)" "x)$

…but this is hard to read, quite fiddly, and not ergonomic.

Another solution might be to define a variable for every symbol used, assigning it the math class “relation”. However, this requires all symbols to be defined beforehand, and single-character variables in Typst have the additional ergonomic issue of requiring a preceding # in math mode. This is arguably even worse than just adding spaces everywhere.

I think the ideal solution would be for the space-removing behavior of math mode to be configurable by a #set statement; the current behavior would become the default, while preserving space around bare single-character variables would become an option that could be enabled. I take it this isn’t currently possible, judging by the number of questions about spacing in math mode (e.g. this one). I’d like to make the case that it should be supported; perhaps this could be incorporated in the design of the upcoming math overhaul which was teased in the description of PR #5738 and in the last comment on PR #4638 :slight_smile:

In the meantime, would it be possible to write a #show rule to change the way spacing is handled in math mode? This could provide a better intermediate solution. I’m imagining something like:

#show math.term: it => {" " + it + " "}

…but my limited experienced with Typst scripting has me stuck on this front.

Thanks in advance!

2 Likes

Show rules can certainly do something but we delve into typst internals (I’m just thinking it might not be idiomatic, at least not yet, or stable across versions of typst.)

Here’s a demo of a simpler rule of that sort:

#let symbol = $x$.body.func()

#let isalpha(x) = x.match(regex("[A-Za-z]+")) != none

// test the function
$x$ #isalpha: #isalpha($x$.body.text), $exists$ #isalpha: #isalpha($exists$.body.text)

#let set-symbol(x) = {
  if isalpha(x.text) {
    math.class("binary", x)
  } else {
    x
  }
}

#let bdot = math.class("relation", ".")
$(exists x bdot f x (g x) = (lambda y bdot h y) x) quad "default spacing"$\
#show math.equation: it => { show symbol: set-symbol; it }
$(exists x bdot f x (g x) = (lambda y bdot h y) x) quad "modified symbol spacing"$

This particular rule says that only variables A-Z, a-z are those you want to space like this. I think you could make a more advanced rule - looking at subsequences and variables follow variables and so on, but it also gets more fragile the more complicated it is.

If I’m not mistaken there is no math class that corresponds to the spacing that you want - the variables should cuddle some operators like exists and lambda (?) but not each other. If there’s a “missing” math spacing class, does it need to be implemented?

1 Like

If you modify the rule you can in fact get the exists and lambda to have tight spacing again. There are multiple ways to do this, but with the symbol rule already in place, it could be there:

#let operators = ($exists$.body, $lambda$.body)
#let set-symbol(x) = {
  if isalpha(x.text) {
    math.class("binary", x)
  } else if x in operators {
    x + h(0em)
  } else {
    x
  }
}

And for this example, “fence” and “binary” do the same thing to the variables, only with larger spacing for fence. They could of course behave differently in some other places. “fence” is an interesting class for your question - at least to see something in typst - items with fence class vary their spacing depending on presence of space characters, while I don’t think the binary class items do.

1 Like

This is an excellent and thorough work-around, thanks @bluss!

1 Like