RFC: Can we remove the `===` operator?

The === vs == operators are a great source of confusion in the languages where they are prevalent. People coming from languages without it expect == to mean deep equality, and people coming from languages with === expect === to be deep equality and == to mean equality after some type coercion. See #1749.

Throughout the standard library, as far as I’m aware, === and == both take the same action. However, the presence of both encourages 3rd party library developers to create unintuitive “sloppy equals” operator overloads, especially when translating from ruby.

I think that cases where == and === differ, if they exist, should be removed, along with the ambiguity the operator creates.

3 Likes

It’s used in Regex, Range and Class. It’s the operator used for case-when. It will not be removed.

There aren’t more appropriate alternatives that could be used in those cases?

1 Like

I guess we could use a different name or operator. I don’t see people confusing these operators, though.

=== is much less commonly commutative than ==, i’d be happy to change it to #similar? instead of being an operator, since most people don’t use === directly, instead only using it indirectly with case.

On the other hand, it’s not a big deal to me, but if someone wants to push a PR through i’m open to approving it

3 Likes

I agree, but similar? is not very accurate. I guess the lack of finding a fitting name is why this method is called === and it probably will stay like this.

And if this was to change, I wouldn’t want to see a PR directly, but a dedicated discussion about the naming first ;)

3 Likes

It looks like Regex#=== can just be changed to Regex#== – there’s no conflicting overload for == on Regex. Either way it’s just a mirror of #match? and ~=.

Range is a bit of a different case0..10 === 5 # => true. Since most other languages use === to mean a sense of especially deep equality, this seems to me to be an extremely unintuitive API.

I don’t really get the difference between what’s happening in Class#== and Class#===.

After looking over these cases I think it would be best to change case to look at #match instead of ===. case statements already do some pattern matching – as evidenced by the examples given. Regex is the most obvious case but I think it would make sense in other cases too.

match would certainly be a feasible option for naming this method. However, assigning implicit semantics to such generic method names is problematic. Developers might not be aware of this relation and define a match method with completely different meaning. But suddenly, an object with that method behaves very unexpected when used in a case ... when context. An operator like === is less likely to be unintentionally implemented and it is more obvious that there might be a special meaning associated. This could be avoided by introducing a module Matchable which would be implemented by Regex & Co, but that adds more complexity than just a single method.

In order to avoid === because it’s not very intuitive, we might consider a different operator. But I think it should be an operator, not a regular method name to avoid accidental implementation. =~ might also work, but it’s already commutative for String and Regex, so the semantics are different. Maybe something like =~~ could do, but I fail to see a huge benefit.

That’s true, you definitely have a point there. match is definitely too generic, and there aren’t any other cases I know of where a normal method like that does something so specific and unique. I definitely rescind that suggestion :slight_smile:

I like =~ as a “match operator” though – it seems intuitive that it would be.

I don’t see what you mean by the semantics being different, could you explain?

After this discussion, it seems to me that we have two operators which both roughly mean “matches” – === and =~.

x === roughly means “belonging to the category denoted by x”. For example, String === means “belonging to the category of Strings”, so String === "foo" is true. For range, 0..10 === means “belonging to the category of things that are in the given range”, for regex is “belonging to the category of strings that match the given regex” and so on.

For Regex it happens to be similar to =~, but for other cases the semantic is not quite “matches”.

From my point of view, I’d prefer not to make fundamental changes to things that are not broken. That === exist in other languages with a different meaning is probably not a problem. The meaning is exactly the same as in Ruby, and Crystal matches most of Ruby’s syntax and semantic.

6 Likes

I’m against this change. This will bring confusion to the users coming from Ruby and create a need to define another sensitive method (think of problems arising from redefining #id).

If it ain’t broken, don’t fix it please.

4 Likes

+1 from me, only because ruby having so many different equals methods (eql? === ==) has always been confusing. It’s not broke but it is ugly. Maybe could. be named…something better? another example (in this case of eql?): https://github.com/crystal-lang/crystal/pull/8893#discussion_r391093981 a mere mortal cannot easily tell what is used for what…FWIW :)