Skip to content
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

explore fixing #5187 via and/or/xor/not and associated updating ops #25180

Closed
wants to merge 17 commits into from

Conversation

Sacha0
Copy link
Member

@Sacha0 Sacha0 commented Dec 19, 2017

(Another exploration in resolving #5187. This approach evolved out of #25156, but presents a better set of tradeoffs and much nicer looking code. Relevant parts of the #25156 writeup are repeated below.)

The crux of #5187: & and | conflate logical and bitwise operations. The natural precedence of & and | for logical operations is low, whereas that for bitwise operations is high. This tension forces an unsatisfying precedence compromise.

One approach to resolving this tension is to use regular functions instead of infix operators for (non-control-flow) logical and bitwise operations, for example and/or/xor/not (as with xor now) and corresponding updating operators and=/or=/xor=/not=. Having additional upsides (see below), this approach at one point or another received support from (e.g.) multiple(1) core(2) contributors(3) on github(4), slack, and triage.

This branch explores what life looks like with the particular names/ops mentioned above. (#25156 explored a different realization of this concept, naming only bitwise operations and not providing corresponding updating operators). This branch is somewhat outdated and incomplete, and only the first half or so of the diff uses the updating operators, so assessing that half is best.

A few unexpected findings from preparing #25156 relevant here:

  1. I was surprised by how infrequently I had to add parentheses to (compound) expressions when rewriting with or/and/not/xor and associated updating ops: In such compound expressions, defensive parens seemed the rule rather than the exception.

  2. I was also surprised that, despite having the precedence table open (or by some point memorized) and knowing the associativity rules, I occasionally had to resort to checking the result of certain compound expressions at the repl to figure out precisely what they were doing.

These observations perhaps support the notion that something is amiss with precedence as it stands (#5187), or perhaps support the notion that relying upon precedence and associativity in compound bit twiddling and eager logical expressions is suboptimal in any case (too difficult to remember, too difficult to mentally parse, or what have you).

What are the tradeoffs of this approach?

Arguments in favor:

With or/and/not/xor and associated updating ops, you never need the precedence table and/or associativity rules on hand (or memorized) to grok bit twiddling and/or eager logical expressions, nor need you interrogate particularly complex expressions at the repl.

Functions or/and/not/xor and the associated updating ops are relatively semantically clear independent of which language(s) you came from, whereas expectations regarding the semantics, precedence, and associativity of infix ops &/|/~ vary depending on which language(s) you came from.

or/and/not/xor and associated updating ops yield substantially nicer looking code than the approach in #25156, providing a much better approximation to the cuteness and compactness of the present infix operators. Particularly, this approach's updating ops are quite pleasant.

This approach immediately frees all of &, |, and ~(rather than only #25156's ~). Uses for free ASCII appear in every discussion of feature introduction or extension (think views, partial evaluation and currying, dataflow pipelining, traits/protocols, missingness, getfield overloading, things namespacing related, et cetera). Dare I say there are better uses for these operators in a language like Julia than bit twiddling.

FemtoCleaner can perform this approach's rewrites (in contrast to those in #25156).

Arguments against:

To those of us who came from C(/-influenced languages), &/|/~(/^) feel natural for bitwise and eager logical operations. (On the other hand, names like those in this approach are common to a number of languages, e.g. lisps, ada, algol, some pascals, et cetera.)

Expressions in or/and/not/xor and associated updating operators are slightly more verbose than in &/|/~ (but unlike #25156's names, the names here retain most of the infix operators' compactness and cuteness).

The advantages of or/and/not/xor hold primarily in compound / complex expressions. In short such expressions, the downsides of &/|/~ are weaker and the upsides stronger, and one could argue that expressions where the downsides of &/|/~ become significant should be refactored.

Other considerations worth bearing in mind:

Though a good fraction of those of us active on github came from C and do / have done a nontrivial amount of bit twiddling, chances are we represent a minority in the broader Julia userbase (and likely will be a shrinking minority with future uptake). Naturalness/intuitiveness for us may not be a good predictor of naturalness/intuitiveness for the broader userbase, perhaps to the contrary.

Typing speed is rarely if ever a productivity rate limiter when bit twiddling, whereas existing-code-grokking speed frequently is.

Thoughts? Thanks all! :)

@c42f
Copy link
Member

c42f commented Dec 19, 2017

In this approach, xor gets a unicode infix operator \veebar, but the others don't?

I guess we could go with the versions from mathematical logic: \vee for or, \wedge for and, \neg for not? Unfortunately \vee=∨ looks very like v.

Overall looking at the code It's a bit saddening to see the brevity and familiarity lost by doing this and the inconsistency of having operators like << combined with function calls. But on the other hand it seems compelling to recover | for high level constructs like piping.

@Sacha0
Copy link
Member Author

Sacha0 commented Dec 19, 2017

Yes, it's very much a tradeoffs game :).

@vchuravy
Copy link
Sponsor Member

vchuravy commented Dec 19, 2017

It would sadden me to see Julia become less ergonomic for bitwise operations and honestly I do not thing that and and or help readability in comparison to the infix operators. I went through the changes and found that it consistently made it harder for me to understand the operations being performed.

The natural precedence of & and | for logical operations is low, whereas that for bitwise operations is high. This tension forces an unsatisfying precedence compromise.
I disagree that the natural precedence of & and | for logical operations is low. It should have the same precedence as the bitwise operations since they are semantically the same operation (just over an Int1 vs an Int8).

I suppose the tension come from the fact that && and || have a different precedence? The precedence there is due so that one can drop parenthesis for conditions, which is a great ergonomics improvement, but in my opinion it would be also fine to require parentheses and use the same precedence rules as for & and |.

If we decide to go for this I would very much like Unicode aliases http://www.fileformat.info/info/unicode/char/2228/index.htm and http://www.fileformat.info/info/unicode/char/2227/index.htm

--edit: I should add that even though I am not a fan of this change, many thanks to @Sacha0 for exploring this.

@StefanKarpinski
Copy link
Sponsor Member

StefanKarpinski commented Dec 19, 2017

I agree 100% with @vchuravy and @ararslan here. The problem with & and | is not that they are infix operators but that they have an unintuitive precedence. There's no other meaning we want to give to these operators – after all, what other meaning would we give to & that wouldn't be incredibly confusing? We could disallow & for bitwise and and only allow it for booleans. But what's gained by that? The precedence of the boolean & is still awkward, so people writing vectorized code would still have to write (a .≤ b) .& (b.foo .== "bar"). Getting rid of boolean & altogether would be no better since then that person would have to write and.(a .≤ b, b.foo .== "bar"). What people want is to be able to write a .≤ b .& b.foo .== "bar".

The two actions we should possibly take are:

  1. Allow .&& and .|| with the obvious lowering in vectorized syntax (definitely). This would allow one to write a .≤ b .&& b.foo .== "bar" without parentheses.
  2. Change the precedence of & and | to match their lazy siblings (maybe). This would allow one to write a .≤ b .& b.foo .== "bar" without parentheses.

@Sacha0
Copy link
Member Author

Sacha0 commented Dec 19, 2017

The problem with & and | is not that they are infix operators but that they have an unintuitive precedence.

& and | only have precedence because they are infix operators 😉.

There's no other meaning we want to give to these operators – after all, what other meaning would we give to & that wouldn't be incredibly confusing?

While & is broadly associated with conjunction, it also sees widespread use as the address-of operator without apparent confusion (and a few other uses besides), so I am not certain this argument holds up. Additionally, | has several common meanings beyond disjunction, and ~ has several meaning as well. (Your related regrets about using &/| for logical operations are partly what motivated me to check this approach out :).)

The other arguments against this approach strike me as much stronger :).

Somewhat orthogonal to this discussion: & and | subsuming both logical and bitwise operations in Julia works well. Is there a reason to retain C's separation of logical (!) and bitwise (~) not?

Thanks all! :)

@Sacha0
Copy link
Member Author

Sacha0 commented Dec 19, 2017

(To be clear, I am quite sympathetic to the arguments against this approach. Nonetheless this approach seemed worth exploring given its combination of interesting tradeoffs and prior apparent support :).)

@StefanKarpinski
Copy link
Sponsor Member

StefanKarpinski commented Dec 19, 2017

Somewhat orthogonal to this discussion: & and | subsuming both logical and bitwise operations in Julia works well. Is there a reason to retain C's separation of logical (!) and bitwise (~) not?

Probably not. (See what I did there?)

@Sacha0
Copy link
Member Author

Sacha0 commented Dec 19, 2017

Probably not. (See what I did there?)

I did and I'm dying. Edit: Well, not dying it turns out 😛.

@cormullion
Copy link
Contributor

Though a good fraction of those of us active on github came from C and do / have done a nontrivial amount of bit twiddling, chances are we represent a minority in the broader Julia userbase (and likely will be a shrinking minority with future uptake). Naturalness/intuitiveness for us may not be a good predictor of naturalness/intuitiveness for the broader userbase, perhaps to the contrary.

This is nicely said.

@andyferris
Copy link
Member

Stefan wrote:

The problem with & and | is not that they are infix operators but that they have an unintuitive precedence. There's no other meaning we want to give to these operators – after all, what other meaning would we give to & that wouldn't be incredibly confusing?

Abstract logical? I think operationally there may be types which behave differently under bitwise operations and "logical" operations - see #25435 (comment)

The other smoking gun is that the precedence desired for "logical" statements (lower?) seems to me to be a bit different to the precedence desired for "bitwise" operations (higher?). Personally, I see two distinct semantics here. We can afford two abstract interfaces (whatever they may be) that happen to overlap for Bool.

(And yes, judging by the kind of people wanting to use Julia, I would guess you are just as likely to get an end user interested in non-standard logic as you are a seasoned programmer who needs to twiddle his bits. Even 3VL with missing is nonstandard logic, which doesn't really make sense with bitwise operations (how many bits are in a missing, again?))

@Sacha0 Sacha0 closed this Aug 6, 2018
@Sacha0 Sacha0 deleted the names branch August 6, 2018 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants