#870 Proposal: bitwise operators

brian Wed 16 Dec 2009

This whole discussion with # for typeof operator got me thinking again of the bitwise operators. I've suggested this before with tepid response, but I want to ask again...

Do we really want consume four prime ASCII symbols for bitwise operators? Other than computing a hashcode, using bitwise operators is very rare for most application code. Is it really that big a deal to use a method call instead of an operator:

line ^ col1 ^ col2  =>  line.xor(col1).xor(col2)

Freeing up these four symbols (and potentially << and >>) would provide a lot more flexibility for the things we do all the time. The | symbol would become unambiguous for closures, and the & symbol would become unambiguous for the field storage operator. Plus it would give us a lot more flexibility in future meta-programming features that likely would be heavily used compared to bitwise operations. Are we ready to free ourselves from this C legacy?

Thoughts?

tactics Wed 16 Dec 2009

Some old conversation on the subject at `http://fantom.org/sidewalk/topic/504`. It seems the general feelings were they weren't used a lot, but they weren't totally unused.

I had to use bitwise ops just the other day to read little endian integers from a stream. They are also used in the compiler for bitfields. But I think as soon as you step above the system/IO level, bitwise ops vanish entirely.

It would be really nice to have the syntax free for other things, especially since the majority of Fantom users will not care about bitwise ops (hell, most of them probably won't even know they exist).

Question. Do the special methods like plus and mult compile down to add and mult opcodes for native types? In other words, can we add methods not, and, or, xor, lshift, rshift to Int and still have them compile down to their respective opcodes without having to invoke the method? If so, I would vote for that. After all, bitmath is usually pretty obfuscated, and it's not like the operators make the expressions much clearer.

jodastephen Wed 16 Dec 2009

My immediate response was no, we need them to help bring existing users of other languages in. But, now I'm thinking that I use them very rarely (mostly hash codes in date-time classes). So, this does make sense I think.

tactics Wed 16 Dec 2009

Another idea to bring to this. If we feel there would be an unfulfilled nostalgia for traditional C bitwise ops, we can always reintroduce them as a DSL.

// standard way of doing things
override Int hash()
{
  line.xor(col1).xor(col2)
}

...

// alternatively
override Int hash()
{
  Bitwise <| line ^ col1 ^ col2 |>
}

tcolar Wed 16 Dec 2009

I use them once in a while, I'll be totally fine with them being a DSL.

But on the other hand, I'm not sure I like those operators to mean something else, this will most likely confuse/mislead somebody new to Fantom (That's the way Scala code often makes me feel when they go overboard with operators).

ivan Wed 16 Dec 2009

+1 for DSL

brian Wed 16 Dec 2009

Question. Do the special methods like plus and mult compile down to add and mult opcodes for native types?

Yes. Both 3 + 4 and 3.plus(4) are emitted to fcode as normal method calls. But the JVM will emit the call as an optimized ladd opcode.

DSL is an interesting idea. Although I'm not sure I'm ready to add a DSL like that to the core. We can always do something like that later (or someone could add it as a plugin). For the sake of this proposal let's say this is between keeping the operators as is or switching to use normal method calls.

So the full proposal is to fully remove the bitwise operators from Int and Bool:

~a      =>  a.not
a | b   =>  a.or(b)
a & b   =>  a.and(b)
a ^ b   =>  a.xor(b)
a << b  =>  a.lshift(b)
a >> b  =>  b.rshift(b)

This would make parsers much simplier for detecting closures since | would be unambiguous. It might also make sense to switch field storage operator from * to one of the old bitwise ops.

andy Wed 16 Dec 2009

I'm on board with this change - and definitely would like to change field storage back to using &.

tactics Wed 16 Dec 2009

I'm on board with this change - and definitely would like to change field storage back to using &.

Isn't it a little ironic to get rid of old C syntax so that we can go back to using something resembling another piece of C syntax :-) But I agree.

DanielFath Wed 16 Dec 2009

This would make parsers much simplier for detecting closures since | would be unambiguous. It might also make sense to switch field storage operator from * to one > of the old bitwise ops.

Wait, aren't there logical operators && and ||? Wouldn't that make | ambiguous since it is used as both logical or and closures?

tactics Wed 16 Dec 2009

No, the tokenizer takes care of that. Having a single pipe generates Token.pipe. Having two pipes in a row generates Token.doublePipe.

In closure syntax, there's never two pipes in succession. It's always |<stuff>|.

Ampersands work similarly.

DanielFath Wed 16 Dec 2009

In hindsight I should have been clearer. I meant from a programmer's PoV not compilers. Though I guess it wouldn't be a problem either way.

As a completely random idea I'd make logical operators verbose (i.e. a or b , a and b ) and make bitwise operators C-like ( || and && ).

alexlamsl Wed 16 Dec 2009

As a completely random idea I'd make logical operators verbose (i.e. a or b , a and b ) and make bitwise operators C-like ( || and && )

Or we can just make the bitwise operators verbose.

qualidafial Thu 17 Dec 2009

Or we can just make the bitwise operators verbose.

I was thinking along the same lines.

1 or 2  => 3
7 and 9 => 1
1 xor 3 => 2

alexlamsl Thu 17 Dec 2009

That was actually a loaded suggestion. The question is whether the following is different enough for Fantom to add sugar for it:

1.or(2.and(3)).xor(4)   // Today.

1 or 2 and 3 xor 4      // Sweeeet~

KevinKelley Thu 17 Dec 2009

1 or 2  => 3
7 and 9 => 1
1 xor 3 => 2

I actually like that. I also like

true and false     => false
true or not false  => true
true xor false     => true

Since we've got clear separation in the type system between booleans and ints, there's mostly no reason for separate operators.

helium Thu 17 Dec 2009

Right, you could use the same operators for bitwise and logical operations depending on whether you operate on integers or booleans.

Do we want a logical xor on Booleans? xor can't evaluate its second argument lazily, while and and or do.

Is this all too different from the popular C-like languages?

jodastephen Thu 17 Dec 2009

I've realised that I do use the logical and and or all the time for booleans.

boolean valid = true
valid &= validateA()
valid &= validateB()
valid &= validateC()
return valid

Its certainly quite procedural, but its also very clear, and I firmly think that the unary | and & should stay on Bool.

I don't care about | and & on Int I also don't care about Not, xor, and the shifts in general.

helium Thu 17 Dec 2009

So you explicitly want validateB and validateC to be called even if validateA already reported that A is not valid? For that you obviously need eager versions of the operators or you have to introduce one variable per validation and perform the and-operations afterwards.

In most cases such validation-tests don't cause side effects so you actually want something like:

return
   validateA() &&
   validateB() &&
   validateC()

brian Thu 17 Dec 2009

So let's explore the notion of using keyword operators. In more DSL friendly languages the way to solve this is allowing you to omit the dot/parens using an infix notation:

a.foo(b)  => a foo b

No question that is elegant. However I really want Fantom to be a TOOWTDI that prevents devolution into dialects. Having that as a general purpose feature seems like it would get abused and make IDE tooling more difficult. So I really don't want to go down that path.

However I would be on board in making not, and, or, and xor keyword operators. But I don't think we can do this unless we also do it for the !, &&, and || boolean operators. That is a much bigger break from Fantom's Java/C legacy. But at the same time using keywords is much friendlier for new programmers. That would also free || up instead of |->|.

So I have no problem switching all the bitwise and logical operators to keywords. But I also worry that may be straying from the C/Java/C# syntax too much.

andy Thu 17 Dec 2009

I think replacing boolean operators with keywords is outside the mission statement of Fantom - so I vote nay on that.

helium Thu 17 Dec 2009

That would still allow to use && and || as bitwise operators on Ints freeing | and &.

DanielFath Thu 17 Dec 2009

My thoughts:

  • I like the verbosity of and, or, xor.
  • !, && and || have a long tradition,
  • !, &, | are easy(er) to write (than and, or, xor) and finally have an appropriate !=, &= and |= operators.

I don't really like not since it would be a bit tedious to write

!((x || y) && z)       //old syntax
not ((x or y) and z)  //new syntax

and more importantly != would have to become not ==

a != b    //old way
not a==b  //new way

So there is another way to do stuff. Keep !, and let &, ^ | become and, xor and or respectively. When used on Bool they behave like logical operator with short-circuiting when used Ints they are bitwise operators, otherwise they serve as a syntax sugar for and( ), xor( ) and or( ).

PS. After thinking about this it here is an idea &, ^, | and ! (or &&, || as alex suggested) would act as logical/bitwise operators for Bool and Int respectively; While and( ), xor( ) , or( ) not( ) would have syntax sugar in a form of infix notation.

class Foo 
{
  ...
  Foo and(Foo arg)
] 

foobar := foo and Foo

Pro:

  • Super short
  • Consistent
  • Can still use &=
  • Free up || , &&

Cons:

  • Confusing for new commers
  • Semantic ambiguity

FanDocs doesn't like empty lines, grrr >:(

x || y //Bool x and Bool y, result Bool
x | y  //is this Bool, Int or something else, and what could be the result?

So which one you like best (or suggest another one)?

/*Current*/
x != y  
!((x || y) && b)

/*Suggestion 1*/
x and y
not ((x or y) and z) 

/*Suggestion 2*/
x != y
! ((x | y) & z)  

Edit: Thanks helium, I fixed it.

A bit more limited suggestion could be to give ^, |, &, ! special treatment.

ivan Thu 17 Dec 2009

As I understand, the reason why there is a historical separation between bitwise and logical operators is the result of the type system lack, which is not a problem today. So I think the simplest way just to remove |, ^, & and use their logical equivalents both for ints and bools. Introducing keywords as logical operators adds noise from my point of view

helium Thu 17 Dec 2009

@DanielFath: You have two open but three closing parenthesis in all your new syntax examples.

brian Thu 17 Dec 2009

OK let's bring the proposal back around to my original intention: free up the bitwise operators and require using normal method calls:

~a      =>  a.not
a | b   =>  a.or(b)
a & b   =>  a.and(b)
a ^ b   =>  a.xor(b)
a << b  =>  a.lshift(b)
a >> b  =>  b.rshift(b)

Existing boolean logic operators ! && || would remain the same.

Like most things this is merely taking a feature away to simplify the overall language and parsing. We are just requiring that bitwise operations use the more verbose method call syntax in order to simplify the overall language.

tcolar Thu 17 Dec 2009

I like Brian's last proposal the best.

It's just simple method calls.

I don't really like adding a bunch of keywords (or, not xor etc..)

The whole discussion is that we can free those operators like & since they are so rarely used. If they are so rarely used I don't think it needs it's own syntax / keywords ... so plain methods is what I vote for.

I would still be careful on not using this operators in a context where they could be confused with their c++/java meanings ... since this could confuse a lot of people.

jodastephen Thu 17 Dec 2009

I still think | and & are too important to remove from Bool. The rest I don't care about and methods are fine.

I agree with not having keyword operators in Fantom, they look and feel wrong.

brian Thu 17 Dec 2009

I still think | and & are too important to remove from Bool. The rest I don't care about and methods are fine.

I've always thought that having bool support both && and & was really confusing. It is a serious left-over from C based on anything non-zero being true (just without the short circuit). I believe the only use case where they make sense is things like:

valid = true
valid &= validateA
valid &= validateB
valid &= validateC
return valid

I've done that on occasion, but its pretty easily re-written in another form. This isn't about removing these operators from a single type, it is about removing them from the language. Otherwise the exercise is pointless - we actually haven't freed the symbols for other uses.

helium Thu 17 Dec 2009

Side effect causing validation code seems odd to me, but you can write it like this which isn't much more verbose:

valid = true
valid = validateA() && valid
valid = validateB() && valid
valid = validateC() && valid
return valid

brian Thu 17 Dec 2009

Side effect causing validation code seems odd to me

I think that technique is used when you want to report each error you find, but keep track of if any errors were found. I actually use that pattern in myself in BuildJava. But I think this sort of code is even more rare than bitwise operators and very easily reworked. So I don't consider it a compelling reason to not simplify the language.

MoOm Thu 17 Dec 2009

The most common use I have for bitwise operators is to combine several flags. For example:

win := Window(Window.FULLSCREEN | Window.BORDERLESS | Window.MODAL)
if (win.flags & Window.FULLSCREEN)
{
  //The window is full-screen
}

The equivalent code with the .or() or .and() methods:

win := Window(Window.FULLSCREEN.or(Window.BORDERLESS).or(Window.MODAL))
if (win.flags.and(Window.FULLSCREEN))
{
  //The window is full-screen
}

Maybe it's because I'm too used to the | and & operators, but I really think the second version looks weird and not intuitive at all. Is there a way to do this kind of things in Fantom without using bitwise operations?

brian Thu 17 Dec 2009

Is there a way to do this kind of things in Fantom without using bitwise operations?

I'd like to enhance Enum to work like C# enum bitmasks eventually. That is a much more elegant solution than using C like bitmasks.

DanielFath Fri 18 Dec 2009

Mostly I agree with brian; I had forgotten why we were changing the language. Still while I agree !, && and || part, I still think syntax sugar for and, or, xor and not methods should be syntax sugar in the form of infix notation.

Sure we'd gain a few keywords but at least writing and/or would be easier. Besides, the usage of such methods would be rare enough to not warrant a symbol operator and often enough so that it somewhat benefits from verbosity.

jodastephen Fri 18 Dec 2009

I'm heavily opposed to and being a keyword here. There is no means to tell whether it means bitwise and or logical and? You can't tell. This is just confusing. (In fact I'd expect that reading this code would be a logical and:

if (valid and isSystemUser()) { ... }

So, I think that bitwise and and or as keywords are inappropriate.

As I've said, xor, and the shifts aren't so significant, and probably should become operators and no-one would notice. But and/or are still pretty core to expectations IMO.

DanielFath Fri 18 Dec 2009

The way I see it why does Bool need and operator any way? It already has &&. Is there some use case where bitwise operator is used and where logical operator couldn't perform as well?

That way when you use syntax:

if (valid and isSystemUser()) { ... }

is just syntax sugar for

if (valid.and(isSystemUser())) { ... }

If you are using keywords (and, or) it means you are doing something which requires you to look into API. I admit it looks a bit weird but when I saw C operator for the first time, coming from Pascal I thought they looked weird too.

KevinKelley Fri 18 Dec 2009

Well, this thread's been going for a while. Here's my opinion:

Keyword operators are an interesting idea, but it's a more general thing than just swapping and for &. At least potentially, it would mean extending function-call syntax ( x = a and b; echo x ) and, while neat, doesn't really fit with one of Fantom's founding principles, which is to be instantly familiar to the Java/C world.

So while I lean toward the idea (aka the Lisp Philosophy) that rather than make a hodge-podge, we should do the minimum of things, but do them absolutely well. I lean that way, but my years in C/C++/Java/C# props me up. There's a lot of legacy there, and we shouldn't flout it without strong reason.

Removing bitwise operators (just doing that; free up the tokens and require the a.and(b) syntax) might be a reasonable place to flout tradition. It's a fairly small area of use; it clears away some old special-purpose stuff that's kind of in our way.

(The use of boolean ops for non-short-circuiting validations, should burn in hell, by the way. Is that not a most twisted way of getting what you want to happen, to happen?:-)

Anyway. Put me down as cautiously in favor of the "remove bitwise operators" proposal.

tcolar Fri 18 Dec 2009

+1 to what Kevin just said.

I also think using .and() could be a bit misleading, somebody might assume it's the same as && (like it does in ruby & python and probably others) so I'll much prefer it to be something like bitAnd() or binAnd() or something (same for or), I know it's longer but the whole point of this is that thos operations are rarely used anyway.

brian Fri 18 Dec 2009

This is what I suggest - I will add a warning to the compiler for the next build if you use a bitwise operator. Everyone can change their code to use normal Int methods and we can evaluate if this is something we can live with. The poor Java guys using BigDecimal have had use methods for all their math ;-)

If we can live without bitwise operators it will give us a ton of future proofing for new features, and potentially allows much more robust parsing of closures.

brian Fri 18 Dec 2009

Promoted to ticket #870 and assigned to brian

jodastephen Sat 19 Dec 2009

I find a.and(b) to mean a && b. I think others will too. This is very misleading.

If we have to make this change, a.bitAnd(b) is a lot clearer.

helium Sat 19 Dec 2009

+1 for renaming the currently existing and and similar methods to bitAnd and similar.

DanielFath Sat 19 Dec 2009

Oh well, at least renaming it makes sense. +1 for bitAnd.

brian Sat 19 Dec 2009

We already have and, or, xor methods on Int - I was not proposing to rename them, only remove their shortcut symbol. I guess the question is it worth making these method names more verbose to be explicit? I personally don't see how things will be confusing. You will continue to use ! && || for boolean logic and then these will be method calls on Ints. There is really only one meeting for not, and, or, xor, shiftl, and shiftr on Ints, so I don't really think using more verbose names really buys us anything.

Take an example from gfx:

// today
override Int hash() { w.hash ^ (h.hash << 16) }

// proposed
override Int hash() { w.hash.xor( h.hash.shiftr(16) }

// verbose
override Int hash() { w.hash.bitXor( h.hash.bitShiftr(16) }

So I definitely vote in favor of short readable names.

DanielFath Sat 19 Dec 2009

Well, I think the rename idea was merely for bitAnd and bitOr (possibly bitXor or bitNot). Shift would stay the same since ambiguity only comes from comparison to logic operators.

andy Sat 19 Dec 2009

Leave as the short names ( and, or, etc) - I think its fine.

KevinKelley Sat 19 Dec 2009

There's Bool.and, .or, .not; and there's Int.and, .or, .not. I think this pretty well follows the standard OO pattern where you overload words to give them meaning in current context.

I don't especially like the bitAnd suggestion, I think it's a case of explaining too much. and means what it always means in its context, and with method-call syntax the context is clear.

helium Sat 19 Dec 2009

and on Bool is a bit strange anyway as it doesn't get it's second argument by name.

x := false && causeSideEffect()   // doesn't cause the side-effect
y := false.and(causeSideEffect()) // does cause the side-effect

But perhaps jodastephen likes it:

valid := true
valid = valid.and(vaildateAAndCauseSideEffect())
valid = valid.and(vaildateBAndCauseSideEffect())
valid = valid.and(vaildateCAndCauseSideEffect())
...

brian Sun 20 Dec 2009

I guess its a separate issue if we still want to keep and/or/not on Bool - I'd be in favor of just removing them, but don't have any strong opinion.

brian Mon 21 Dec 2009

I added a warning to the compiler which deprecates the bitwise operators. You just need to replace the operator with its corresponding method:

~   Int.not
|   Int.or
^   Int.xor
&   Int.and
<<  Int.shiftl
>>  Int.shiftr

Note that I deprecated Int.inverse, use not instead. I also deprecated lshift and rshift (moving the l/r to the end) to be consistent with Str.justl, justr, padl, and justr.

I was using the bitwise operators in very few places (hash codes, some color stuff). I actually think using method syntax is almost just as readable since it makes the parens more uniform. So I am definitely thinking getting rid of the bitwise operators is the right way to go for Fantom.

brian Thu 14 Jan 2010

Its been about a month with the bitwise operators being deprecated.

Any comments or feedback?

I'm thinking I'll be fully removing them for the next build.

liamstask Thu 14 Jan 2010

They only pop up in a few places in my code, but I haven't had any unhealthy reactions to the current implementation. Works for me.

KevinKelley Thu 14 Jan 2010

I've converted several of my older projects to the no-operator style; in each case the long list of warnings seemed a bit daunting, but it turned out to be not so many actual changes. Even when you are doing bit-ly things, the uses only occur in a few places; most code is doing other things.

So, my opinion is to go ahead and remove em. Ill be happy to stop getting errors about inconsistent bitwise-or on my closure code. :-)

Yuri Strot Tue 19 Jan 2010

+1 for removing bitwise operators at all. Int methods work fine everywhere.

A slight offtopic: I'm sure more than 50% of hash and equals methods in every application are property based. So why we need to override it every time? In java I'm always using IDE's code generation for this. But I believe the simpliest solution would be to have a special facet for example:

@unique=["w", "h"]
const class Hints
{
  ...
  const Int? w
  const Int? h
}

If we need property based hash and special equals, we can use this facet and override only equals method. I'm also sure this kind of facet can be used in another place like mapping to relational database. What do you think?

katox Tue 19 Jan 2010

@ystrot This is a tough problem - there have been some debates on this topic, for instance 209, 376 and 378.

Yuri Strot Tue 19 Jan 2010

Thank you katox. There are really interesting discussions. Let me share some thoughts on all this stuff.

Use compare in equals, hash in compare, equals in compare, ...

Actually I don't like this idea. Just because these methods can be used in different areas. compare for sorting, hash for HashMap optimizing and equals for object identifying. So I don't see any advantages in restriction one area of usage by another area.

Use Comparable mixin instead of default implementation.

The only reason I see for this is have equals implementation based on compare. But it's very simple to implement and have some exceptions. In other cases it usefull to have default compare implementation to sort any list and print it, use for binary search and so on. Hash-based implementation is interesting, but as default implementation lexical order looks more user-friendly (may be it useful to have utility hash-based Comparator).

equals/compare for hierarchical classes

I believe it's so special that no make sense to talk about as a default behaviour. By default equals should check only the same type. If you need something special - just create your own implementation for your special case. However for general usage I like some mentioned ideas like limited multi-methods.

Back to my original proposal. What I want is just remove a lot of unnecessary code. By the way: currently in fantom 23 classes which have hash / equals (except native classes). All this implementation is property-based. This is also only one class compiler::TokenVal which uses different properties for hash and equals. Also only 2 of 23 classes compiler::Location and flux::Mark also have compare implementation.

I can provide statistics for my code, but I think it's the same: in most cases hash / equals are property-based and there is no explicit comparing mechnism which should be defined in a class instead of using comparators.

I've also think that facets on slots look more elegant but it doesn't provide order and doesn't allow to use slots from a base class.

andy Tue 19 Jan 2010

Lets start a new topic to discuss this so we don't hijack this one ;)

brian Thu 21 Jan 2010

I've removed the bitwise operators from the language grammar and compiler.

Those of you with alternate Fantom parsers will also probably want to remove these operators from your grammars.

brian Thu 21 Jan 2010

Ticket resolved in 1.0.49

Login or Signup to reply.