I want to know your thoughts about hijacking `def ==` for other purposes

When desiging any kind of expression DSL, there is a need to create an expression for equality.
Please consider:

trait Expr[T] {
    def same(other :Expr[T]) :Expr[T] = Eq(this, other)
}
case class Literal[T](value :T) extends Expr[T]
case class Eq[T](left :Expr[T], right :Expr[T]) extends Expr[Boolean]

The human mind being what it is, one naturally tends to use == to create such comparisons, leading to code like

   e1 == e2

instead of

  e1 same e2

Scala allows to define a method == which will be used instead of equals, if present, and there is no technical reason why it couldn’t be used for the above purpose - equals can be always invoked explicitly. This is the more desirable because other standard operators - <, <=, &&, ||, etc. do not introduce ambiguity and are regularly used in this manner. There is even a precedent insizeIs in the standard collection library already. The downside of course, is that someone wanting to really invoke equals, will often write == for the same reasons, even if aware about these shenningans.

This isn’t a huge problem either way: one use case expects Boolean as the return type, the other Expr[Boolean], so as long as there is no implicit conversion Boolean => Expr[Boolean], both kinds of mistakes will almost certainly be caught by the compiler. It is still a minor nuiscance however, and because I personally lean too much towards ‘magic’, I wanted to probe if hijacking == for this purpose would make people angry, because ultimately what matters is minimizing programmers’ annoyance.

To champion this idea a bit further, one can both have the cake and eat it, so to speak:

trait Expr[T] {
    def ==(other :Expr[T]) = Eq(this, other)
}
case class Eq[T](left :Expr[T], right :Expr[T]) extends Expr[Boolean]
implicit def unliftEquality[T](e :Eq[T]) :Boolean = e.left == e.right

In the above case, == will work both as regular equality and meta equality, depending on the context,
and I can’t see it introducing tricky bugs. However, I know some people who would vehemently oppose such a solution on principle, so I’d like to poll how ‘the community’ feels about the idea.

Well, you asked for opinions, and here is mine.

If I would be reviewing a PR and I see someone defining their own == (in the context of real equality) without a clear definition of why such a custom equality is needed I would reject the PR.
Now, if such == doesn’t even compute the equality of both objects but rather returns something else (even if that thing is conceptually is an equality) I would reject it right away no matter the explanation.

IMHO, you would be better off using a different operator === & =:= both come to my mind, although both are already somewhat standard and have some slightly different meaning, I think in the right context it makes sense. Especially =:=, since === is maybe more common as stricter equals.

1 Like

Fair enough, we have already crossed swords with regard to strictness vs flexibility before :slight_smile:
If it’s not ==, then =:= or === is no better than any other name (other than perhaps having the right associativity) - in practice I always write == on my first try anyway.
May I ask how do you feel about sizeIs then? Or defining <=, && for custom types?

BTW, a good - IMO - use case for overriding == was type-safe equality (before Scala 3, but even in Scala 3 it’s simpler than providing required givens).

/shrug
IMHO, it reads better and is more natural than same, but I get the point.

I don’t use it much since I rarely need to care about the size of a collection.
However, I, indeed, have used it once or twice, and is great that it is optimized.
I don’t think it is wrong in that context since it is essentially equality.

As long as they behave as expected I don’t see why not.
Although, for <= is usually better to go the Ordering route.

I personally prefer to use === for that.
Any Scala developer knows that == is not type-safe, having to be constantly checking when it is and when not is worse, IMHO, than adding an extra character.

Also important to note is that you cannot ever override ==. You can only overload it.

1 Like