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

A good way to do this is to define

!x = Base.:!(x)
!(x::Integer) = (x == 0)

in your code. Then there’s not even any piracy.

5 Likes

I guess I’m missing something, how is that definition different from:

(waiting to learn from the Jedi (oops) Julia Master!)

Edit: maybe I get it now, it’s overwriting ! in my package, not extending it, right?

Yes, it’s creating a new ! inside your module that defaults to Base.! behavior for everything but Integers. This way everybody else who uses ! will continue to get the standard behavior (unless they import ! from your module).

The one difficulty is I’d like to make that definition easily available by just using a module, and I don’t see how to do that without extending Base.:!

You would have to say using Foo: !.

I thought that would give a conflict (you know, use must use Base.xxx and Foo.xxx)

Folks - let me know what you feel about my proposal, consolidated from other’s ideas here and on GitHub, and join in the Tomatina (a festival in Spain of throwing tomatos) :grinning:.

!(val::Integer) = (val == 0) Please don’t do this. I love that values non-booleans don’t have magic comparison properties like in Python, it’s just too many special cases to remember. Also it would be weird if if !a end worked for a variable where if a end errors.

9 Likes

If ! is used for both logical and bitwise not, couldn’t foo &= ~0x00ff become foo &= !0x00ff which is equally concise?

Special cases? I actually think of it as being a very generic rule, no special cases at all. ! returns a boolean based on the type of the value. That seems very Julian to me. Just like the relational operators can be used with many different types of values, but they always return a boolean value.
Just as if iszero(a) ... works, even if if a doesn’t, so should if !a work.

The problem there is that anybody used to most of the languages in the world expect that ! returns a boolean value, and never anything else (just the Rust people don’t, that I’m aware of so far)

I think that foo ∧= ¬0x00ff would be more likely to be understood, people aren’t used to using those Unicode operators, but they may very well know that they are boolean operations. foo and= not(0x00ff) is also less likely to confuse people coming from the many C-like languages.
(there are many languages that in other aspects are not at all like C, but that follow C’s conventions for logical and bitwise operators, like PHP, Perl, Ruby, or some or most of them, like Python or Rust).

3 posts were split to a new topic: Argument techniques for bit-twiddling

@garrison made this comment on GitHub:

I think many people against this change will agree even with this statement. As I understand, the question is whether !x for non-Boolean x should be unimplemented and thus error (friendliest to developers coming from other languages; prevents “unexpected” behavior), or instead perform a bitwise not (saves a symbol for use elsewhere; provides “consistency”).

If !x for non-Boolean is left unimplemented in Base, then it can be extended as desired by people like me who want to use it as a generic operator, as Jeff showed, without affecting anybody else’s code.
If it is made to perform a bitwise not, while it saves a symbol, that can be done in other ways (again, see my proposal A Modest Proposal (for !, &, | and ~)), also, it is more likely to cause confusion, as it violates the convention in most languages that ! always returns a boolean value (no matter what it is applied to), and finally, I feel it doesn’t really provide consistency with other boolean operators (such as the relational operators, which also take arbitrary input, and always produce a boolean output).
I think that knowing that if !a is always valid is way more consistent than having it work or fail depending on whether a is a boolean value or something that can be bitwise complimented.

I actually consider this to be the strongest argument, in practice, for going through with this change. While it seems like it easily could have been controversial, I’ve yet to find a single blog post deriding the Rust developers for this choice (or even a single complaint, anywhere). Of course, Julia is meant to be more approachable than Rust, but many Rust developers will be coming from C where !x means iszero(x), so if it has not been confusing there, perhaps it will not be here either.

I also googled the issue and asked on some Rust discussion boards, and did find a number of comments wanting to know what the Rust bitwise complement operator is, people seeing that everything else matched C,
they expected ~ to work.

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

I feel it’s worth pointing out for the discussion that C has different operators for these because it couldn’t dispatch on type. So, unlike Julia, its logical operators aren’t simply just a subset of the behaviors of the bitwise operators, but actually distinct operations (the logical operators include an implicit call to the bool operator). It’s by no means decided that we should change this, since it’s mentally quite surprising to those of us used to C. So it’s also a worthwhile datapoint that there is precedent from the rust developers having chosen to do the same (and it doesn’t sound like the experienced developers regret it – beyond the additional entry in the “notable differences” that new users need to memorize if coming from C).

7 Likes

On GitHub, you’d commented:

Just fwiw, this isn’t what it means in C++, and various style guides may mention not to write out x when used as in an inferred boolean expression as x != 0 to avoid surprises in template code. See also “The Safe Bool Idiom”, since after the number of ways you could overload or implement the conversions that could be applied to this expression, it’s pretty hard to know what if (x) means without checking the documentation for the type of x.

Hmmm, I’ve been using C++ since it first came out in 1986 for the PC (Zortech C++), and that’s not my understanding at all.

What it means, according to the C++ standard, is that !x is the negation of the result of applying the bool operator to x.

If the operand is not bool, it is converted to bool using contextual conversion to bool: it is only well-formed if the declaration bool t(arg) is well-formed, for some invented temporary t.

The result is a bool prvalue.

For the built-in logical NOT operator, the result is true if the operand is false. Otherwise, the result is false.

Note the following also:

All built-in operators return bool, and most user-defined overloads also return bool so that the user-defined operators can be used in the same manner as the built-ins. However, in a user-defined operator overload, any type can be used as return type (including void).

Also, both bool and ! can be overloaded in C++, and are done so in the standard library:
Here is just one example: http://en.cppreference.com/w/cpp/io/basic_ios/operator!
The normal behavior for bool applied to an integer is just as I’d stated, it is the same as != 0,
and there are many many ways that bool is overloaded (note that it’s not necessary to overload ! in most cases, because simply by overloading bool, !val will behave as expected for whatever the type of val is.

I was thinking about technical problems of proposal and till now found only two:

  1. backward incompatibility with (quite natural?) !(x) = iszero(x) in some packages

This is not technical but (more) “political” problem. I am talking about discomfort to change these packages. My personal feeling is that it is not big problem but I could be wrong. Till now I see only Scott who is probably using it and maybe somebody (me?) could help him with transition. (although it could be problem if he has close sourced packages!)

  1. bitstring(!0x00) != bitstring(~0x00)

This is technical change where we probably have to think more if it could spoil something or not.

I suspect that we need new definition for bitstring(x::Bool) too!

1 Like

It puzzles me to see a late-game change like this. The argument for having !/&&/|| over not/and/or was that it was compatible with “C” standard. I would also note that ! is semantically overloaded in Julia to mark mutable functions. Too many symbols in an expression create significant barriers to recognition. As someone coming from Python world, I really miss the readability of not, and and or.

Further overloading of ! may have an impact on readability and maintainability. For example, there are many legacy applications that use 1 and 0 for boolean values. Currently, if you accidentally use ! on them you get a clear error (in Python it’d do the “right thing”, IMHO). Either approach is fine. However, I think the proposed approach of having !1 silently become -2 may lead to some hair pulling for novice users. I didn’t even know that the bitwise & and | operators were overloaded with eager boolean logic semantics; that said, it didn’t matter since I used only && and ||.

While it’s perhaps dead-on-arrival, I’d once again ask if and, or, and not could be added so that higher-level business logic programmers doing data analysis could avoid symbolic soup. Thanks.

3 Likes

That would be part of my proposal (A Modest Proposal (for !, &, | and ~)), which is based on the proposal by @sacha (explore fixing #5187 via and/or/xor/not and associated updating ops by Sacha0 · Pull Request #25180 · JuliaLang/julia · GitHub). Note that when applied to boolean values, and and or would act as non-shortcircuiting logical operations (as do & and | currently), and not(someboolean) would act the same as !someboolean.

1 Like

I also really like that. That is something that I would love to see in Julia.

If you like it, then go express your support for at-sacha’s PR 25180 on GitHub, and/or my proposal to take that PR and also add Unicode operators (suggested by Simon Byrne I believe for not being ¬).

(just forget my other ideas for now about using ! generically and & when it is free)

Novice users likely don’t encounter such code. And if they would, the current ~1 == -2 isn’t much better. I rather see a hiccup for experienced users, as !1 == -2 goes against all tradition.

This has been discussed here (5238, here (19788) and here (25180) at least. (19788 lists language comparisons for logical operators based on Rosetta). – Those (partly longstanding) issues didn’t lead to action.

And imho the choice is arbitrary. From the comparison it seems, the more obscure a language, the more likely is usage of 'and-or-not' and from the discussion it seems, the more core/senior the (Julia) developer the less enchanted by this proposition.

Your ‘higher-level business logic programmers’ remark gave me the thought, that maybe it is even important to prefer '&&', '||' and '!' to signal that Julia is not some slow wishy-washy hip language but fast, respectable and suited for many tasks besides doing data analysis. :wink: :rocket: – More honestly I think, go, rust, swift, R, Matlab, javascript and of course C have choosen and there is no reason to deviate. (And certainly not in the last minute). I did like the 'and-or-not' style from Pascal, but when having to write '&&' etc. in R, it really was not important.

4 Likes