What is the precedence between assignment operator `=` and the anonymous function operator `->`?

Julia parses the command

f = x -> x^2      # (1)

as

f = (x -> x^2)    # (2)

This parsing is reasonable, because expression (2) is clearly what any user would mean in a real application.

However, Base.operator_precedence gives that the = operator has higher precedence than the -> operator, so strictly speaking (1) should be parsed as

(f = x) -> x^2    # (3)

As far as I can tell, this is not a well-formed expression.

Question 1: When a literal application of the precedence specified by Base.operator_precedence leads to an expression that is not well-formed, does Julia always automatically reparse the original expression in a way that leads to a well-formed expression? Or does it sometimes throw an error and require the user to manually parenthesize the expression so that it makes sense? If the former option is correct, then how does Julia do so?

But the plot thickens. Julia actually accepts line (3) as a valid expression without returning an error!

Question 2: Is line (3) a valid command? If so, how does Julia interpret it? If not, why doesn’t it return an error?

Moreover, if we assign

g = ((f = x) -> x^2)   # (4)

and try to evaluate g for any argument (e.g. g(5)), then we get ERROR: UndefVarError: x not defined. My first guess was that line (4) would return an error, and my second guess was that calling g(5) would first assign f = 5 (per the inner parentheses) and then return 5^2 and discard the temporary variable f. But both guesses are wrong.

Question 3: What is the behavior of the anonymous function g ?

Furthermore, if we instead define

h = ((f = x) -> f^2)   # (5)

then the anonymous function h behaves just like command (1) does, and simply squares its argument!

Question 4: The anonymous functions g and h defined in lines (4) and (5) seem equivalent. Why do they behave differently?

Question 5: I believe that commands (3), (4), and (5) are all ill-formed and should return error messages, so the behavior of g and h is undefined and the failure to return an error message is a bug in Julia. Am I correct?

The only relevant information I could find online is the comment

Yes, -> has asymmetric precedence because it is very asymmetric: very few things can go on the left side (basically only a symbol or symbols) but any expression can go on the right side. x -> y = x is a valid function that contains an assignment.

at Operator precedence for unlisted operators & anonymous functions · Issue #14933 · JuliaLang/julia · GitHub, which doesn’t fully answer my questions.

I’m running version 0.5.0.

1 Like

It’s just an optional argument.

julia> f = (x=1) -> x
(::#3) (generic function with 2 methods)

julia> f()
1

julia> f(2)
2
2 Likes

Oh, wow. Duh, that was embarrassingly simple. Thanks! But why doesn’t Julia parse expression (1) as (f = x) -> x^2, since the = operator has higher precedence than the -> operator?

Note

help?> Base.operator_precedence
  operator_precedence(s::Symbol)

  Return an integer representing the precedence of operator s, relative to
  other operators. Higher-numbered operators take precedence over
  lower-numbered operators. Return 0 if s is not a valid operator.

that is, Base.operator_precedence(:(->)) being 0 means that :(->) is not considered a valid operator.

In reality this is somewhat misleading: indeed -> is of course a valid operator, but it does not have a true precedence. As mentioned in the parser itself, -> is unusual: it binds tightly on the left and loosely on the right.

3 Likes