On adjoints and custom postfix and infix operators

I am not sure I understand why custom precendence is so important for you.

Very few programmers remember the precedence tables in full, most people just rely on a few rules and use ()s for explicit grouping. Precendence mistakes are frequently a source of bugs, some style guides discourage relying on precedence altogether, eg the Google JS style guide says:

Optional grouping parentheses are omitted only when the author and reviewer agree that there is no reasonable chance that the code will be misinterpreted without them, nor would they have made the code easier to read. It is not reasonable to assume that every reader has the entire operator precedence table memorized.

I am sure customizing precendence could be useful occasionally, but at the same time it could make these bugs an order of magnitude worse.

5 Likes

Shouldn’t variable names beginning with those characters have been deprecated before they became allowed as operator suffixes. For example in Julia 0.6 the following is legal syntax:

ᵃx = 3
1+ᵃx # returns 4

but in Julia 0.7 it gives UndefVarError because it parses as 1 +ᵃ x.

Edit: I opened an issue on github. https://github.com/JuliaLang/julia/issues/28441

It’s important to me because I sometimes work with binary operations that use the same symbols that are already declared, but at a different precdence. Not being able to change the precedence makes the operator declaration practically useless in most cases. It isnt completely necessary, but it would be nice to actually be able to use infix operators proper, without worrying about wrong precedences.

Normally, I dont have many issues with Julia, but since this is one of my issues, it stands out, it feels like a language wart to me.

1 Like

Can you give an example?

I may misunderstand your intention, but if you want an operator to have different precedence based on its arguments, I don’t think you can do that; because that is already resolved in the AST, eg

julia> dump(Meta.parse("a+b*c"))
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol +
    2: Symbol a
    3: Expr
      head: Symbol call
      args: Array{Any}((3,))
        1: Symbol *
        2: Symbol b
        3: Symbol c

Just want to ask where can I find the definition of precedence of those UTF-8 operators? (except by reading the source code flisp) I think it might looks more explicit if this part can be define in Julia.

And maybe we will need a function for looking up the current precedence between two operators (or inside an expression). It will be more convenient when you are trying to use some unfamiliar operator.

You can use Base.operator_precedence for reflection.

2 Likes

See https://docs.julialang.org/en/latest/manual/mathematical-operations/#Operator-Precedence-and-Associativity-1

2 Likes

That is correct, this is what I specifically mentioned when I brought this problem up, and this is also why I characterize it as an issue.

However, it is not impossible to think of a solution. Here is an example of how this could work

Base.unhinge_precedence(current_module())
Base.set_precedence(char, prec)

Perhaps, you could make it even simpler into a single function call

Base.set_precedence(current_module(), char, prec)

Essentially, you want the precedence to be context specific for whatever module you are currently in, to contain the changes. Then the parser can keep its current default precedence list, and if a specific module needs a different precedence, then you can simply create an Array of precedence lists that maps to specific modules that had changes made. Then all you do is copy the default precedence list into the new one, except that the new one is mutable instead of immutable. Then you simply let the user change the precedence.

Thus, the parser then simply needs to have some basic logic to check if the precedence has been altered in a specific module, and if it has, branch off to select from the mutable Array instead of the immutable default.

if module_precedence_modified
    # pick from mutable array
else
    # pick from immutable default
end

Fairly simple I think, and it doesn’t cause any breaking changes or compatibility issues.

If this is really so terrible for normal users and for the mainstream status of the language, please do elaborate and provide specific counter points, so that I can be convinced that this is a terrible idea. Right now, I am not convinced this is a bad idea, and I don’t think this would bother anyone, and if you don’t need it, then you would probably never notice that this features was added. If you need it, then win-win.

This kind of syntax flexibility may seem useful in certain cases, but I would hate to be the person tasked with debugging code in which operator precedence is different from what everybody expects.
Good luck finding the bug if for example a+b*c^d is being parsed as ((a+b)*c)^d instead of
the customary a+(b*(c^d)).

5 Likes

I don’t think so, I guess that allowing “local” parser states and designing a composable API would involve a fundamental redesign of the parser. And, as @jandehaan pointed out, it would be a code readability nightmare.

That said, I am sure it can be done (again, there are Lisps with very flexible parsers you can rewrite to parse anything), but I suspect there is a reason why this did not catch on.

Frankly, beyond “it would be nice”, I don’t understand the appeal. You can always use ()s for grouping subexpressions — instead of a nuisance, think of them as a feature: they help the person who reads this code 3+ months later.

Precendence tables are a bit like national anthems: there is a part that more or less everyone knows, but there are obscure parts like the second verse which only the author and a few other people know about. Relying on these to save a few ()s is usually not considered good practice.

6 Likes

Exactly! :slight_smile:

This is a change that has to happen eventually regardless, in my opinion. There are other ways in which the Julia language is context-ambiguous, so moving in this direction is a good step towards resolving that.

Yet anyone writing a math paper seems to have no trouble parsing these precedences, mathematicians dont get confused because they studied all the definitions precisely. Unfortunately, the Julia language can’t make itself smarter because people reject this idea.

1 Like

Certainly, as precedence (order of operations) in algebra is usually considerably simpler, because there are essentially 3 levels (+-, */, ^), with right-associativity for nested exponentiation. Julia as 13 precedence classes (if I am counting correctly), you are proposing to add to these and make them context-sensitive.

I don’t think this would make the language any “smarter”, just some syntax potentially more confusing.

6 Likes

This is all very abstract to me (well, obviously). Can you give any concrete examples of what the code should look like, and what it does look like now? What does the math itself look like?

To me it seems reasonable that if, say, your multiplication operator doesn’t behave like an ordinary * and has different precedence, then it also should have a different spelling — not just in code, but even in writing. Given an example, it might be clearer why this is useful.

Are you even using * or are you talking about more exotic unicode symbols that happen to not have the right precedence for your use? In the latter case, would it make more sense to argue for changing the precedence of those specific operators?

1 Like

I’ve never encountered a math paper that violates the norm that ^ has higher precedence than * which has higher precedence than +. Moreover, if I did come across a paper that did this and missed the “notation” section where they explained their bizarre choice of precedence, I’d interpret everything in the paper incorrectly and be deeply confused when trying to follow it. It would never even occur to me that they might have changed this and my only reaction upon discovering their choice would be confusion and anger that the authors had wasted my time with such a ridiculous choice.

9 Likes

This is just brilliant!

4 Likes

This would not be a good way to implement this feature. The problem is that it requires the ability to evaluate julia code in order to parse julia code.

Being able to change operator precedence seems like the opposite of reducing ambiguity and context-dependence.

9 Likes

Yes, this is my case. I am using atypical symbols, but their precedence is hard coded incorrectly. They are not your standard algebra symbols, so they certainly dont interfere. And my use case is very limited to research purposes, there are likely very few users who will ever look at it, let alone contribute. With the current way it works, I’m gonna have to give users a warning that they need to use parenthesis, because Julia will parse the syntax incorrectly without them. This is technically a bug, since I cant tell Julia which operations have precedence. None of these operations involve regular algebra symbols.

So scheme cant handle this? Also, isnt there a Julia parser written in Julia?