#240 Same operators (===)

tompalmer Sat 14 Jun 2008

I'd like to make a humble recommendation of eliminating the === and !== operators. Might be too breaking at this point. My concern is how it introduces programmer burden to have to think about which one to use when in most cases they are either equivalent or the right choice is == (or !=). If enums make the == method final, then there's no issue there, either. The runtime could happily inline to pointer checks.

There may be some value in testing pointer equality still, but it should be rare enough to be more awkward. I recommend a static method such as Obj.same(a, b).

brian Mon 16 Jun 2008

I think the notation of reference equality is pretty fundamental to any language, so I can't imagine living without it. Although unless you really need it, you shouldn't ever have to care and can always use == and !=. I suspect you've looked at some of my code where I've used === as an optimization when I know the values are interned. That is just a temporary thing because I haven't added those smarts to the compiler (where the smarts really belong).

Whether we use === or Obj.same, I personally prefer the operator rather than clutter up the Obj namespace. Although I'd be open to hear arguments for/against.

tompalmer Mon 16 Jun 2008

Hmm. I agree that Obj isn't a nice place to add clutter. Maybe under Sys instead? I just think it would be nice to have == a lot more obvious than ===/'same'. Again, for easier decision making.

otomodachi Mon 16 Jun 2008

I don't think that reference equality could be conceptually placed inside the referred object itself. IMHO the object can only tell the structural equality, so if we are into mapping the operators to methods (or vice versa), I would say structural equalty should be Obj.equals(that) and reference equality should be Sys.same(ref1, ref2).

I don't like either having two different operators looking almost identical (== and ===), so I would choose another operator shape for testing reference equality: I propose using is. This way it can be much more obvious whether you are checking for structural or reference equality.

BTW it would be great to have a "preview" button when posting :-)

andy Mon 16 Jun 2008

is is already taken to check inheritance:

class A {}
class B : A {]  

a := A.make
b := B.make

echo(b is A) => true
echo(a is B) => false

I personally like the === operator over a method call. Given its meaning is similar to the == operator, it sort of makes sense they look similar.

alexlamsl Tue 17 Jun 2008

I would like put a +1 vote for making reference equality a slot in Sys as well.

It feels analoguous to Java's System.identityHashCode() when retrieving the underlying Object.hashCode() after being overriden by a sub-class.

brian Wed 18 Jun 2008

Sounds like a couple people think it belongs as Sys.same versus ===, and I can see that. I still lean towards the operator because:

  • as a systems language reference equality seems pretty important
  • the compiler can do comparison type checking
  • it efficiently compiles down to a fast opcode

But if it isn't used very often, then we probably don't need the complexity of an operator. When I add that optimization to the compiler I will go thru all the code and see how often we still need that operator - if it isn't used much then we can fallback to a method.

JohnDG Wed 18 Jun 2008

I like ===, personally, as it's a standard in several other languages, and referential equality is both natively supported by the JVM and essential to many core algorithms.

tompalmer Wed 18 Jun 2008

Brian, I like the "convert and see" strategy.

jodastephen Thu 19 Jun 2008

I support keeping === as an operator for reference equality. It matches other languages and is easy to grasp. I definitely don't think it should be hidden away as a method.

Separately, I think that the is operator would be better as isa, as that is more descriptive of what it does. Saying that, if I were in charge, I'd just use instanceof for operator, as I think that is even more clear (and its part of Java I don't think is broken).

brian Thu 19 Jun 2008

The is and as operators come straight from C#

alexlamsl Thu 19 Jun 2008

I would like to be educated on usages of as over is. On (somewhat rare) occasions when I use is / instanceof the branch logic tends to be more than return null.

For reference, the example given on MSDN isn't very convincing.

brian Thu 19 Jun 2008

Most of the time you use is (or instanceof) in conjunction with a cast:

if (foo is Bar)
{
  bar := (Bar)foo
  bar.doSomething()
}

Fan and C# combine the type check and cast into one operation:

bar := foo as Bar
if (bar != null) bar.doSomething()

In practice, I almost always use the as operator instead of the is operator.

alexlamsl Thu 19 Jun 2008

Good point. But how about for the following code?

if (foo is Bar) {
  bar := (Bar) foo
  bar.balance
} else if (foo is Car) {
  car := (Car) foo;
  car.accelerate
} else {
  spin(foo)
}

andy Thu 19 Jun 2008

Its probably mostly a matter of taste. I primarily use it when I know the type, since I find its cleaner than the cast (especially if inside parens):

f := ((Int)x).toHex
f := (x as Int).toHex

It also happens to map to a single opcode in the CLR.

brian Thu 19 Jun 2008

Actually I would write that code (anything with more than one if/else) as a switch:

switch (foo.type)
{
  case Bar.type: bar->balance
  case Car.type: car->accelerate
  default: spin(foo)
}

EDIT: actually that code won't work with inheritance (duh!). Probably if I had to write that code I'd use -> to keep the code cleaner:

if (foo is Bar) 
  foo->balance
else if (foo is Car) 
  foo->accelerate
else 
  spin(foo)

tompalmer Fri 20 Jun 2008

A mix of thoughts. The compiler should be able to do autocasting in cases like this (though only more reliably than the duck calls in cases like local vars not modified in closures):

if (foo is Bar) {
  foo.balance // The compiler can insert the cast because it knows the context.
} else ...

Also, if nullable ? is introduced, then we could change the meaning of as like so (and potentially get rid of other forms of explicit inline casting):

a := x as Int // throws NullErr if null and CastErr unless Int
b := x as Int? // same behavior as current Fan/C#

And finally, === doesn't mean the same thing in all languages that have it. In ECMAScript, it means "same value and same type" (as compared to == that does type conversion first such as between Numbers and Strings), but when applied to any user-defined Object types, it still effectively does pointer equality (and == does too for that matter). The differences between === and pointer equality are subtle in ES. In the end, I still think having both operators makes for harder decision making by programmers. But if it lives in the end, I don't think it will ruin the language.

I'm not saying that any of my above ideas are vital or perhaps even good (and it's not my language anyway), but I think they are worth thinking about.

(Side note: I'm terrible about proofreading before submitting. My individual edits don't send out multiple emails, do they? I just subscribe in batch mode.)

andy Fri 20 Jun 2008

Only new posts queue an email - so no, your edits won't send out dups :)

tompalmer Fri 20 Jun 2008

Thanks much for verifying that.

helium Fri 20 Jun 2008

I just want to mention what C++ allows:

if (Bar * bar = dynamic_cast<Bar*>(foo)) bar->doSomething()

which would look in Fan like this:

if (bar := foo as Bar) bar.doSomething()

The scope of the variable is only the coresponding if-branch.

alexlamsl Fri 20 Jun 2008

That one liner is really handy, especially if it works in Fan already!

I always thought the implicit casting within the instanceof if-block would be a handy feature for Java. But if we can fit that inside the condition, there is less point to introduce a feature that might introduce surprises.

tompalmer Fri 20 Jun 2008

There could be some surprises for Bool values in there, but it does seem nice otherwise. Hmm.

brian Sat 21 Jun 2008

if (bar := foo as Bar) bar.doSomething()

That is neat, but looks like it relies on the fact that any non-zero value is considered true in C/C++. In a language like Fan where Bar can never be evaluated as a Bool I'm not sure how it could work.

But this kind of reminds me of the null checking syntax sugar on the other thread. If you have a compact way to do express "do this if not null", then maybe that could be used like Helium's example.

tompalmer Sat 21 Jun 2008

I actually love the "truthiness" style from languages like Python and ECMAScript where classes can be customized to be evaluated in boolean fashions (and note that this is very different from implicit conversion to boolean). But I'll save that subject for a different thread sometime.

otomodachi Sat 21 Jun 2008

The previous example looks nicer with Groovy's Safe Navigation operator:

(foo as Bar)?.doSomething()

...doesn't it?

helium Sat 21 Jun 2008

OK, then an a bit more complicated example:

if (bar := foo as Bar)
   whatever.doSomething(bar.someMethod())

How does Groovy handle this?

Of course this could conveniently be handled by monads. Something pseudo-Scala-like:

for (bar <- foo as Bar)
   whatever.doSomething(bar.someMethod())

You could even handle a lot more complicated cases:

for (bar <- foo as Bar, qux <- baz as Qux)
   bar.doSomething(qux.something())

But monads are hard to understand for most people. In this special case you could just imagine that all values are collections of zero (when it's null) or one element and the statement just iterates over these collections like nested for-each loops.

tompalmer Sat 21 Jun 2008

I agree that Scala style outside most common experience. Option types and their behavior like Lists in Scala works well there, but I personally don't think it's good for Fan.

helium Sat 21 Jun 2008

Well, monads don't come from Scala but from category theory. The most well known use in programming languages is in Haskell. I just used Scala as an example, because it's a more Fan-like. Monads are very powerfull. In Haskell they're used for example for an exception system, for continuations, parser combinators, state, IO, ... . (Scala has (like most languags) built in exceptions and state so you don't use monads for that there.)

tompalmer Sat 21 Jun 2008

I know monads go way back, not that I understand them all the way. Back a few years, I toyed with Haskell for a few days, and it was cool, but I never learned it well. I know Scala much better than other functional languages, so I reference that more often.

brian Thu 26 Jun 2008

When I add that optimization to the compiler I will go thru all the code and see how often we still need that operator - if it isn't used much then we can fallback to a method.

Since this discussion, I've used the === operator several times - and I really did want to check reference equality, not the result of equals. So I'm going to make the call that the === and !== operators stay as is.

tompalmer Thu 26 Jun 2008

OK. Thanks for the update.

Login or Signup to reply.