Pipe operator quiz! Can you guess the result?

Question 1

A=[1,2,3];
B=[1,1,1];

A.>B |> sum

Question 2

1:2 .|> x->x^2 |> sum |> inv

Question 3 (warning: spoilers)

The behaviors above seem just… evil! :wink:. 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:

help?> |>
search: |>

  |>(x, f)

  Applies a function to the preceding argument. This allows for easy function chaining.

  Examples
  ≡≡≡≡≡≡≡≡≡≡

  julia> [1:5;] |> x->x.^2 |> sum |> inv
  0.01818181818181818
2 Likes

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))
6 Likes

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  |>
sum

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.

2 Likes

@Tamas_Papp I don’t think this rule of thumb really works in practice. At least you probably want to add [], ., ^, // and =, probably a few others. Nobody wants to write

(obj.field) = ((A[3]) // (x^2))

instead of

obj.field = A[3] // 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
sin.(0:.1:pi)

# 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.

3 Likes

Similar things in R:

library(magrittr)
> A = c(1,2,3); B = c(1, 1, 1)
> A > B %>% sum
[1] FALSE FALSE FALSE
> 1:2 %>% function(x)x^2 %>% sum
Error: Anonymous functions myst be parenthesized
> 1:2 %>% (function(x)x^2) %>% sum
[1] 5

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, == and > 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.

2 Likes