A Plea for Bit-Twiddlers! (please keep ! as logical negation, separate from bitwise negation!)

I would like to make a plea on behalf of all present and future bit-twiddlers who also love the power of Julia.

Would a mathematician be happy if they were told that they had to lose one of their most frequently used operators?
Masking operations are so prevalent, foo &= ~0x00ff for example, or something like:
(flags & ~(STR_LATIN | STR_ASCII)) == 0.

Also, it would be nice to actually have ! defined on Integer values, i.e. !(val::Integer) = (val == 0),
which would also help with a lot of the bit twiddling. (and that would allow for !!val to quickly convert an integer value to a Bool, something frequently desired, instead of val != 0.

@jeff.bezanson, do you really want to make things even harder for people doing lower-level programming in Julia? :wink: Pretty please, save the tilde!

all - 1

6 Likes

Some of us feel it’s silly to use up two ASCII operators for almost the same meaning. The only real difference between them is that ! throws an error for non-Bools.

I prefer iszero(x) or x == 0 to !x for the other purpose; this doesn’t come up often enough to make the extra verbosity intolerable.

But the decision on this is still somewhat up in the air, since also for many of us, years of C programming make it hard to read !x as bitwise not.

6 Likes

That’s a personal preference - what for you may not come up frequently, might be all the time for somebody else doing a different type of programming.

I counted over 600 cases in base alone that would benefit from being able to say !x instead of x == 0.
There were 46 of ~ in one file alone (bitarray.jl, which is just the sort of bit-twiddling I do all the time).

Note: if you must free up ASCII operators at the expense of us poor bit-twiddlers :-), then instead of combining things that are definitely viewed as distinct operations in C & C-like languages, logical and bitwise not, and instead use ¬ for bitwise not, and maybe even go whole-hog and use instead of | and for &, freeing up 3 ASCII operators.

‘¬’: Unicode U+00ac (category Sm: Symbol, math)
‘∨’: Unicode U+2228 (category Sm: Symbol, math)
‘∧’: Unicode U+2227 (category Sm: Symbol, math)

2 Likes

Even so, prior to this nobody was clamoring for an iszero operator. Would it apply to floating-point numbers too?

I can completely understand wanting concise syntax for bitwise not, but ! works fine for that. It’s not like such code would become horribly verbose or harder to write.

I can’t respond there, however, @stevengj said:

+1. !x means iszero(x) in C, but in C-like languages you can also use x for !iszero(x) in a Boolean context. (Python is even more permissive about booleans.) Since we are strict about the latter, it makes no sense to me to use !x for iszero(x).

Allowing ! to accept Integer values would not represent a huge permissiveness about booleans, it’s a single unary operator, well understood in many languages to transform 0/non-zero values into true/false, and it does really help the succinctness of a lot of bit twiddling code, where &, ~ and ! are used in conjunction very frequently, to check if all the masked bits have no 1s set.

Using non-ASCII characters for all of the bitwise operators would be a bit of a pain (I’d probably bind some simple key sequences to them in Emacs!), but not really too much, and would be consistent with xor now being a Unicode operator.

Yet the math guys wanted to save a single * in 2*n, and just be able to write 2n.
I think it all goes to what type of code you are typically writing.

Suggested in consolidate ~ and ! by Sacha0 · Pull Request #25435 · JuliaLang/julia · GitHub

There was some discussion of these operators in ∧ and ∨ as aliases for && and || · Issue #17472 · JuliaLang/julia · GitHub

2 Likes

Well, I have Base.:!(val::Integer) = iszero(val) defined for my code, so I wasn’t clamoring for it, but this merger would make that not possible to do, so then I would be clamoring! :slight_smile:

Can’t we have both?

That is, for really really core stuff like the former unary prefix ! , and infix xor, take some unicode operator and also define some ascii sequence (e.g.: not , xor) as synonyms? Because I really agree, typing two extra characters is no problem, but using non-ascii unicode looks like a bigger pain than just giving up on infix xor / prefix not.

PS. The point is that not x should parse as not(x), just like x xor y should parse as xor(x,y). Yes, this is a magic extra rule for the parser, but making an extra rule that xor is an infix operator does not sound worse than any other multi-char infix operators like &&.

1 Like

Yes, and when I saw that, I decided that it would be a good compromise, it would keep C/C-like language people’s eyes from burning when they saw ! used for bitwise negation :-O, would mean I could still extend ! to Integers (yeah, I know, type piracy) in my own bit-twiddling programs, would not make my code any less concise to read (just maybe an extra keypress if I add an Emacs binding), and would free up 1 (or 3!) ASCII characters.

Well, that was actually about using them for && and ||, which I wouldn’t be happy with, as using && and || for short conditional statements seems to be very idiomatic Julia, and at least to me, is very readable, the && and || standing out visually from the rest of the expression.

Rust also uses ! for bitwise NOT:


:+1:

I’m not a bit-twiddler, just curious. What is the problem with using ! for bitwise negation? I cannot see where you actually spell it out.

1 Like

To quote from https://github.com/JuliaLang/julia/pull/25435 :

& represents both logical and bitwise and. Similarly, | represents both logical and bitwise or. In contrast, ! and ~ separately represent logical and bitwise not.

That sounds like a really convincing argument. Is this about habit? (I can sympathize, but…)

Rust is very much an outlier in that, otherwise, the rest of the logical and bitwise operators are separate, and all use the C syntax. Note: I’m not sure how happy Rust programmers are with that choice though, if you look, you’ll find questions on places like StackOverflow about it, or people asking “where’s the bitwise NOT in Rust?”

That’s not true at all! Check any C/C++/C#/Java/JavaScript/Python/Swift/Objective-C/PHP/Perl reference:
& is bitwise AND, | is bitwise OR, ^ is bitwise XOR, ~ is bitwise NOT
Go is slightly different, in that a unary ^ is used as bitwise NOT (! is logical not), and they have the nice bitwise combination operator &^, ANDNOT.

Except for Python, which uses and, or, not for the logical operations, the languages also agree that the following are logical operators:
&& is logical AND (it also does short circuit evaluation, but that’s beside the point).
|| is logical OR (again, happens to do short circuit evaluation)
! is logical NOT (like the above operators, that means that 0 is false, non-zero is true)

It is very clear, the most popular (if you go by the TIOBE index, which does have its problems) computer languages keep logical and bitwise operators separate, with most all using the exact same operators as C.

Does Julia really want to confuse people reading code, just to save a single ASCII operator?

There’s no reason now that ~ cannot be extended for other non-Integer types, or have a separate binary ~ operator, separate from the unary ~, that can be used however you want.

I wasn’t able to comment on https://github.com/JuliaLang/julia/pull/25435, but I’m surprised nobody corrected @sacha on that point.
It seems that the whole reason for that PR is based on that misconception.

I presume that he was referring to the state in the Julia language. Is the statement not correct in that regard?

No, it is not.

 &(x, y)

  Bitwise and.

  Examples
  ≡≡≡≡≡≡≡≡≡≡

  julia> 4 & 10
  0
  |(x, y)

  Bitwise or.

It is true that they all work on Bool as well, Bool is <: Integer.

… of which logical & and | are special cases, as Bool is acting like a bit in most contexts (regardless of its representation in memory).