A.>B |> sum
1:2 .|> x->x^2 |> sum |> inv
Question 3 (warning: spoilers)
The behaviors above seem just… evil! . Can we fix this? Changing the operator precedence is breaking, but is there really code out there relying on this behavior?
Credits: First example from personal suffering. Second example adapted from this discussion, itself adapted from the documentation:
Applies a function to the preceding argument. This allows for easy function chaining.
julia> [1:5;] |> x->x.^2 |> sum |> inv
For any programming language, learn the 5 most obvious precedence rules (maybe in Julia
&& ||), and use parentheses for everything else. Otherwise you get bugs like this, as
julia> Meta.show_sexpr(:(A.>B |> sum))
(:call, :.>, :A, (:call, :|>, :B, :sum))
The problem is people tend to use the pipe for multi-line operations where visually it’s less obvious to them that they would need to do that. Like:
(1, 2) .|>
x -> x^2 |>
I mean, I agree that its always best to use parentheses when things are ambiguous, but I’ve seen similar code as the above so many times in the wild that I don’t think it’s even in people’s mind that this could be interpreted the way it is.
@Tamas_Papp I don’t think this rule of thumb really works in practice. At least you probably want to add
=, probably a few others. Nobody wants to write
(obj.field) = ((A) // (x^2))
obj.field = A // x^2
I agree in spirit: it’s best to use parentheses than rely on non-obvious precedences that few people know by heart (for example when using bitwise operators).
But the problem here is not the ambiguity, it’s that Julia’s behavior is counter-intuitive. In the examples above, it’s almost obvious what the precedence “should” be, so users won’t expect parentheses to be necessary.
I think it’s especially bad with the
|> operator: as @jonniedie says it’s used to write computation steps line-by-line, so users really don’t expect they have to add parentheses there.
Another reason I find the high precedence of
|> annoying: this operator provides a nice way to build complex computations incrementally in the REPL (only for quick operations obviously). It’s a habit I took from working in Unix shells. It looks like this:
# look at some values in the REPL
# add some filter (get previous command with Ctrl-p)
sin.(0:.1:pi) |> x->filter(>(0.5), x)
# that was bad, let's fix it (get previous command with Ctrl-p)
sin.(0:.1:pi) |> x->filter(>(0.4), x)
# OK now let's finish (get previous command with Ctrl-p)
sin.(0:.1:pi) |> x->filter(>(0.4), x) |> sum
At the end of the process I have a nice one-liner I can save or share with someone. But it’s much less convenient if after
Ctrl-p I have to move around to add parentheses…
Possibly. People have very heterogeneous intuition about operators that are not common in math and/or language-specific. I don’t feel very strongly about this either way, but since it is a breaking change, I imagine that very good reasons would be requied to change this. The best way forward is probably opening an issue.
Generally, with Unicode, Julia allows a very large number of operators with new ones addes all the time, so relying on intuition for precedence is a bit shaky — “intuition for operator precedence” is something people disagree about all the time. One can classify operators to various classes (eg “like +”) as the parser does, but that again is not intuitive for all operators.
Which is why I suggest parenthesizing almost everything nontrivial. Yes, I agree that it is somewhat inconvenient, but I think it has payoffs.
Similar things in R:
> A = c(1,2,3); B = c(1, 1, 1)
> A > B %>% sum
 FALSE FALSE FALSE
> 1:2 %>% function(x)x^2 %>% sum
Error: Anonymous functions myst be parenthesized
> 1:2 %>% (function(x)x^2) %>% sum
For Question 1, R behaves the same as julia.
To add something to Tamas, I think the “intuition” can also depends on the context. For example,
> should have similar precedence, and what should be the “intuition” for this one?
sin.(1:3) .== 1:3 .|> sin
For Question 2, R can give the error message when it is “ambiguous” because
%>% uses “non-standard evaluation” which is similar to macro in julia. Maybe a similar idea can also be tested in various julia pipe packages?
And I think it is really a good practice to parenthesize anonymous functions, except in very simple cases.