(Opinions needed) Likely upcoming changes to math mode precedence

I think one example is that both sin(x) and sin x are valid notation and common to use, and so a function doesn’t seem to be quite right, at least not if we want to allow the markup to mimic the math notation closely.

I see. But then my next question: Why is op not a Typst function, while math.abs is? (And if it is, why isn’t sin then, which is a special case of op?
With other words, why don’t we implement op as a Typst function and circumvent the problems with that?

op is a Typst function that you can use to make operators like sin:

#let mysin = math.op("sin")
$ mysin(x) $

image

Here mysin(x) is not a function call, it’s the value mysin followed by (x). But the value mysin was obtained by calling op. You could also write

$ op("sin")(x) $

and get the same result.

4 Likes

You can already partially do this by sacrificing one of the symbol accents that works as a function already:

#let sin = sym.circle
#show sin: math.sin
#let accent-char = sin([]).accent
#show math.accent.where(accent: accent-char): it => {
  math.sin + $(it.base)$
}

These now render the same,
but the first line is an actual function call.
$ sin(x) \
  sin (x)
$
2 Likes

I would also vote for option B for consistency with other similar cases such as x^-1 which is rendered as x^(-) 1. See also the related issue: Raising to the power of a negative number should not require parenthesis · Issue #5722 · typst/typst · GitHub

1 Like

Thanks @Y.D.X for the visual summary.

Just to clarify as the case with f_i (x) shows no representation on the “Next version” side: I guess this pattern will still be supported by the next version and render the same?

this pattern will still be supported

Yes; I omit it because I think f_i (x) will become rare in the next version. Just likef (x) today, which I believe most people would write f(x) instead.

1 Like

Just a data point. I was also hit by this today as I was translating my brother’s thesis from LaTeX to Typst.

$
"CF"(X, k)  &= limits(sum)_(x in X) I_k(x) & quad "Class frequency"          \
$

screenshot_2025-07-14_20:03:23

I would thus also be in support for option B.

3 Likes

I’m not a mathematician so I could be wrong, but I support option C or D for the following reasons:

  1. Symbols of functions, like f, can be standalone. I mean, when we say there is a function, we don’t have to say f(x). In this case, f is math symbol. And f(x) is a kind of operation to map x to something else via function f. In most cases, attachments are symbols, not operations. Because of this, when we write $f_i(x)$, we would not expect i(x) to be something attached, unless i is a user-defined “programming” function (stated as below). In this case, treating the i(x) part as the attachment seems to be strange.

  2. Some special functions, like “sin”, they are predefined symbols, although it could be rare to see “sin”, “cos”, etc., alone.

  3. However, abs(x) represents “|x|” in math. Thus, “abs” itself is not a symbol; instead, “abs(x)” the whole thing is. Because abs is a programming function, not a mathematical symbol or operation. A standalone abs does not make mathematical sense.

  4. For other user-defined functions, I think they would probably prefer keep the whole thing together. As an example, I defined a function to show conditional expectation, like

     #let Exp(x, ..args) = $E lr((#x mid(|) #args.pos().join(",")))$ 
    

    When I write $e^Exp(x,u)$, I definitely want the part like $E(x|u)$ to show at the corner, instead of a “E” at the corner but “(x|u)” not.

    Another example is the Imaginary number. If we define a function

     #let Im(a,b) = $#a + #b i$
    

    then I also expect $e^Im(1,2)$ to show something like “e^(1+2i)”, not “e^1 + 2i”.

Overall, I think it would be better to act differently for different components. If the attached component is a “programming” function, like abs and user-defined functions, treat the whole thing as the attachment. For math.op and normal math symbols, don’t show them like function calls.

3 Likes

I don’t know if it makes a difference for your argument, but from $e^Exp(x,u)$ you’d get “e^Exp” followed by “(x, u)”, not “e^E” followed by “(x|u)”; for $e^Im(1,2)$ you’d get “e^Im” followed by “(1,2)”, not “e^1” followed by “+ 2i”.

In other words, the issue isn’t that the function is evaluated and only part of the result becomes the superscript; it’s that option B would not call the function, and put the parameters outside the superscript.

3 Likes

I know I’m a bit late, but I think that an Option F is missing from the suggested solutions: Always greedily match subscripts as far right as possible. That is, f_i(x), f_1(2+2) or g_3(y) would put everything to the right of _ into the subscript. This eliminates all ambiguity, and lets the user determine this with spaces.

This behavior mirrors what new users encounter with implicit multiplication: Writing ab + x produces an error, as ab is parsed as a function. Thus, they have to write a b + x. In exchange, they get functions without leading delimiters (# or \), leading to much cleaner syntax.

The same can be applied for subscripts. In long expressions, reducing parentheses makes code more readable. Letting users control subscripts with x_2(i+1) or f_2 (i+1) achieves this.

Cons:

  • Requires more spaces in the simple cases such as f_1 (x)
  • For simple subscripts, this contradicts the expected behavior from LaTeX, unlike the proposed solution.
  • Is a breaking change, (with wide-spread effect)

Pros:

  • Achieves the same unambiguous parsing as the proposed solution
  • Allows using spaces as a typographic tool, leading to more readable syntax-code than wide parentheses.

I hope this option can be considered – I think a minimal syntax is a key strength of Typst, and is a goal that should be pursued.

6 Likes

I’m a bit late, but still wanted to add a voice in favor of option F for the reasons presented in above comments. Editing and reading sources with many parentheses is more annoying imo than if we can use spaces (see #6442 (comment)).

Additionally, I would be in favor of making any Unary or Vary operator cause the immediately following element to be scripted as well, so that $e^-x$ would work as expected (and we can still write $e^- x$ if we only want the minus sign to be in the exponent): Raising to the power of a negative number should not require parenthesis · Issue #5722 · typst/typst · GitHub

2 Likes

Some time has passed since I’ve asked for feedback and there were quite a few responses on the PR and here in the Forum. There were some voices for most of the Options (stay as-is, revert to 0.3, runtime-dependant parsing), including for an option not included in my blog post: Basically Option A on steroids, where we expand the current behaviour to numbers (Option F).

If you’re interested in the individual arguments, I invite you to reread the discussion as I can’t fully reproduce everything here. But as a quick summary: The main argument in favor of Option B was predictability while the main argument in favor of Option A and F was terseness, primarily avoidance of additional parentheses, which can make equations harder to read.

There is no way to make everyone happy here, so it’s going to be a difficult decision either way. Personally, I believe that predictable, consistent, and intuitive syntax is more important than a bit of extra tersity (this is also in line with Typst’s generally design philosophy). While I wouldn’t say there is clear consensus, the majority of upvotes on both GitHub and in the Forum seem to share this sentiment. For this reason, I have decided to move forward with Option B.

Thanks to everybody who left feedback, in particular also those that argued for other options.

10 Likes

Has it also been considered to make x^-1 parse as x^(-1)?
This is something I stumble over again and again, and I feel like there are few cases where people would like to write x^- 1. But of course it would make the system more inconsistent

1 Like

Personally, I think making it more inconsistent again for a small gain in brevity would be a step back. That’s how we got ourselves into the whole situation being discussed here in the first place.

1 Like