-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Operator precedence of & and | is surprising as element-wise boolean operators #5187
Comments
There might not be a single precedence ordering perfect for all cases. The main problem here is that the result of |
I kind of suspect that our precedences could use a little tweaking, but yeah, every choice is bad in some way. |
Yes, exactly. I definitely think the precedence for bitand is the right choice. That's what led to my brainstorm of the Perhaps it's just my Matlab-think that causes this confusion, where |
+1 for .&& and .||. It seems totally consistent with .*, etc. My understanding is Numpy only ended up using & for element-wise "logical and" since they ran out of infix operators. It's a constant source of confusion (eg, from http://wiki.scipy.org/NumPy_for_Matlab_Users,
Also this is anecdotal, but I suspect taking the element-wise "logical and" of boolean vectors is a more common operation than bitwise operations and so if & is going to maintain its current meaning, it might be worth tweaking the precedence to be the same as .*. |
I must admit that the fact that the priority order differs between Using the same operators for so different things as bitwise and boolean operations is not ideal. Does any other language use that pattern? In Matlab and R Adding |
What is the difference between bitwise and boolean? The difference between |
Yeah, |
Those are fair points. Maybe something like .&? It would be consistent with most binary operators having a '.' version that is element-wise, and could have the expected precedence (later than .<). |
@JeffBezanson IIUC, The whole thing is quite confusing because of the multiple meanings of operators...
So Honestly, the issue of short-circuit really seems secondary to me; of course, when working element-wise it doesn't make sense to short-circuit. But I see that as a side effect. The fact that Matlab offers the choice between two operators (though @StefanKarpinski Yeah, I was thinking too that wasting |
&& does not have multiple meanings. The short-circuit behavior only makes A function like & but accepting only boolean arrays would be silly. The
|
But what about operator precedence? If they are the same, why don't |
The thing is, short-circuiting is not secondary, but essential. If it were I guess I would consider changing the precedence of &. But the key is to
|
Regardless of whether the precedence of & changes or a third set of boolean operators is introduced specifically for element-wise boolean arrays, I think it's very important to allow syntax such as |
+1 for what @mbauman said. I would also throw into the mix that Perl and Ruby have |
I would love it if Julia had |
But currently Apparently [1], open my $fh, '<', $filename or die "A horrible death!"; I'm not sure that's what you want to encourage in Julia. ;-) |
Yeah, I already do that sort of thing all the time with |
@johnmyleswhite +1 to that |
To give a justification for why I like |
I guess our C bias leaked through by picking We could lower the precedence of |
Is there no mechanism for operators to be deprecated? If that happens over the course of months, it seems easy enough to replace |
Agreed. I don't see why we couldn't migrate from
We could also just have both. |
I'd really prefer that we not have both in the long-term future. If that were the final option, I'd prefer sticking with the unloved |
Good point that unlike most changes, this can be handled by search and |
Very true. Let's just open another issue for debating the change of |
+1 for lowering the precedence of |
Precedence is often a damned if you do, damned if you don't kind of business, but I have to say that I've often wished that these operators had lower precedence. |
I really like this idea, IMO I find it really ugly to always write |
Could we allow
Are there any arguments against allowing |
Coming back to this, it seems pretty clear to me that |
For anecdotal evidence, the current precedence of hflights[.&(hflights[:Month] .== 1, hflights[:DayofMonth] .== 1), :] |
(Perhaps I should note that I have WIP on this front. I am prioritizing things A_mul_B though, so not certain whether I will post that work on a short timescale. Best!) |
Giving us space to allow lowering this to `.&&` and `.||` in the future. Ref #5187.
Giving us space to allow lowering this to `.&&` and `.||` in the future. Ref #5187.
Has the plan for precedence change of I haven't seen any update in the 2.5 years since the deprecation. |
I have long wanted a proper fix for issue #5187. It was the very first Julia issue I filed. This is a shot at such a fix. This PR: * Enables parsing for `.&&` and `.||`. They are parsed into `Expr(:call, :.&&, ...)` expressions at the same precedence as their respective `&&` and `||`: ```julia-repl julia> Meta.show_sexpr(:(a .&& b)) (:call, :.&&, :a, :b) ``` * Unlike all other dotted operators `.op` (like `.+`), the `op`-alone part (`var"&&"`) is not an exported name from Base. As such, this effectively lowers to `broadcasted((x,y)->x && y, ...)`, but instead of using an anonymous function I've named it `Base.andand` and `Base.oror`: ```julia-repl julia> Meta.@lower a .&& b :($(Expr(:thunk, CodeInfo( @ none within `top-level scope' 1 ─ %1 = Base.broadcasted(Base.andand, a, b) │ %2 = Base.materialize(%1) └── return %2 )))) ``` * I've used a named function to enable short-circuiting behavior _within the broadcast kernel itself_. In the case that the second argument is a part of the same fused broadcast kernel, it will only evaluate if required: ```julia-repl julia> mutable struct F5187; x; end julia> (f::F5187)(x) = (f.x += x) julia> (iseven.(1:4) .|| (F5187(0)).(ones(4))) 4-element Vector{Real}: 1.0 true 2.0 true ``` * This also enables support for standalone `.&&` and `.||` as `BroadcastFunction`s, but of course they are not able to short-circuit when used as functions themselves. Request for feedback -------------------- * [ ] A good bikeshed could be had over the names themselves. We could actually use `var"&&"` and `var"||"` for these names — and it'd _almost_ simplify the implementation, but in order to do so we'd have to actually _export_ them from Base, too. It seems like it might just be confusing. * [ ] Is this the implementation we want? This uses `Broadcast.flatten` to create the lazy function needed for short-circuiting the second argument. This could alternatively be done directly within the parser — perhaps by resurrecting the old 0.5 broadcast parsing behavior. Someone else would have to do that work if they wanted it. * [ ] Do we want to support the stand-alone `.&&` and `.||` `BroadcastFunction`s if they cannot possibly short circuit?
I have long wanted a proper fix for issue #5187. It was the very first Julia issue I filed. This is a shot at such a fix. This PR: * Enables parsing for `.&&` and `.||`. They are parsed into `Expr(:call, :.&&, ...)` expressions at the same precedence as their respective `&&` and `||`: ```julia-repl julia> Meta.show_sexpr(:(a .&& b)) (:call, :.&&, :a, :b) ``` * Unlike all other dotted operators `.op` (like `.+`), the `op`-alone part (`var"&&"`) is not an exported name from Base. As such, this effectively lowers to `broadcasted((x,y)->x && y, ...)`, but instead of using an anonymous function I've named it `Base.andand` and `Base.oror`: ```julia-repl julia> Meta.@lower a .&& b :($(Expr(:thunk, CodeInfo( @ none within `top-level scope' 1 ─ %1 = Base.broadcasted(Base.andand, a, b) │ %2 = Base.materialize(%1) └── return %2 )))) ``` * I've used a named function to enable short-circuiting behavior _within the broadcast kernel itself_. In the case that the second argument is a part of the same fused broadcast kernel, it will only evaluate if required: ```julia-repl julia> mutable struct F5187; x; end julia> (f::F5187)(x) = (f.x += x) julia> (iseven.(1:4) .|| (F5187(0)).(ones(4))) 4-element Vector{Real}: 1.0 true 2.0 true ``` Co-authored-by: Simeon Schaub <[email protected]>
I have long wanted a proper fix for issue JuliaLang#5187. It was the very first Julia issue I filed. This is a shot at such a fix. This PR: * Enables parsing for `.&&` and `.||`. They are parsed into `Expr(:call, :.&&, ...)` expressions at the same precedence as their respective `&&` and `||`: ```julia-repl julia> Meta.show_sexpr(:(a .&& b)) (:call, :.&&, :a, :b) ``` * Unlike all other dotted operators `.op` (like `.+`), the `op`-alone part (`var"&&"`) is not an exported name from Base. As such, this effectively lowers to `broadcasted((x,y)->x && y, ...)`, but instead of using an anonymous function I've named it `Base.andand` and `Base.oror`: ```julia-repl julia> Meta.@lower a .&& b :($(Expr(:thunk, CodeInfo( @ none within `top-level scope' 1 ─ %1 = Base.broadcasted(Base.andand, a, b) │ %2 = Base.materialize(%1) └── return %2 )))) ``` * I've used a named function to enable short-circuiting behavior _within the broadcast kernel itself_. In the case that the second argument is a part of the same fused broadcast kernel, it will only evaluate if required: ```julia-repl julia> mutable struct F5187; x; end julia> (f::F5187)(x) = (f.x += x) julia> (iseven.(1:4) .|| (F5187(0)).(ones(4))) 4-element Vector{Real}: 1.0 true 2.0 true ``` Co-authored-by: Simeon Schaub <[email protected]>
I have long wanted a proper fix for issue JuliaLang#5187. It was the very first Julia issue I filed. This is a shot at such a fix. This PR: * Enables parsing for `.&&` and `.||`. They are parsed into `Expr(:call, :.&&, ...)` expressions at the same precedence as their respective `&&` and `||`: ```julia-repl julia> Meta.show_sexpr(:(a .&& b)) (:call, :.&&, :a, :b) ``` * Unlike all other dotted operators `.op` (like `.+`), the `op`-alone part (`var"&&"`) is not an exported name from Base. As such, this effectively lowers to `broadcasted((x,y)->x && y, ...)`, but instead of using an anonymous function I've named it `Base.andand` and `Base.oror`: ```julia-repl julia> Meta.@lower a .&& b :($(Expr(:thunk, CodeInfo( @ none within `top-level scope' 1 ─ %1 = Base.broadcasted(Base.andand, a, b) │ %2 = Base.materialize(%1) └── return %2 )))) ``` * I've used a named function to enable short-circuiting behavior _within the broadcast kernel itself_. In the case that the second argument is a part of the same fused broadcast kernel, it will only evaluate if required: ```julia-repl julia> mutable struct F5187; x; end julia> (f::F5187)(x) = (f.x += x) julia> (iseven.(1:4) .|| (F5187(0)).(ones(4))) 4-element Vector{Real}: 1.0 true 2.0 true ``` Co-authored-by: Simeon Schaub <[email protected]>
I have long wanted a proper fix for issue JuliaLang#5187. It was the very first Julia issue I filed. This is a shot at such a fix. This PR: * Enables parsing for `.&&` and `.||`. They are parsed into `Expr(:call, :.&&, ...)` expressions at the same precedence as their respective `&&` and `||`: ```julia-repl julia> Meta.show_sexpr(:(a .&& b)) (:call, :.&&, :a, :b) ``` * Unlike all other dotted operators `.op` (like `.+`), the `op`-alone part (`var"&&"`) is not an exported name from Base. As such, this effectively lowers to `broadcasted((x,y)->x && y, ...)`, but instead of using an anonymous function I've named it `Base.andand` and `Base.oror`: ```julia-repl julia> Meta.@lower a .&& b :($(Expr(:thunk, CodeInfo( @ none within `top-level scope' 1 ─ %1 = Base.broadcasted(Base.andand, a, b) │ %2 = Base.materialize(%1) └── return %2 )))) ``` * I've used a named function to enable short-circuiting behavior _within the broadcast kernel itself_. In the case that the second argument is a part of the same fused broadcast kernel, it will only evaluate if required: ```julia-repl julia> mutable struct F5187; x; end julia> (f::F5187)(x) = (f.x += x) julia> (iseven.(1:4) .|| (F5187(0)).(ones(4))) 4-element Vector{Real}: 1.0 true 2.0 true ``` Co-authored-by: Simeon Schaub <[email protected]>
I just got bit by the python-like operator precedence for the operators
|
and&
. As bitor and bitand, it's a wonderful choice (making checking for bit flags much simpler — a common gotcha in C).But when behaving as their element-wise boolean counterparts to
||
and&&
, it is surprising.A < 1 || A > 2
must be written differently if A is an array:(A .< 1) | (A .> 2)
. Perhaps all that's needed here is a bit more documentation (i.e., putting https://github.com/JuliaLang/julia/blob/master/src/julia-parser.scm#L1-L19 into the Mathematical Operators page, or as something to mention to users coming from Matlab).But as I look at the source for
|(::StridedArray,::StridedArray)
, I see that it's actually applying the bitwise operator to all elements. As a radical alternative, what about adding.&&
and.||
with similar precedence to&&
and||
that ensures boolean elements? Functionally,&
and.&&
would behave the same on logical arrays, but they'd each have the precedence one would expect in each context. (Of course, there'd still be some cognitive dissonance here as the elementwise boolean operators couldn't short-circuit like their scalar equivalents).The text was updated successfully, but these errors were encountered: