Pre-SIP: Assignment Operator Precedence Exceptions (!=, <=, >=)

History

Date Version
Mar 10th 2019 Pre-SIP

Introduction/Motivation/Abstract

This SIP proposes to change the Scala language specification’s assignment operator language definition1, by modifying the 2nd exception from the operator is one of (<=), (>=), (!=) to the operator begins with one of (<=), (>=), (!=).

The aim is to support additional comparison operators that also begin with <=, >=, and !=, and also end in =.

Motivating Example

This was encountered when creating a TypeSafeEquals2 implementation with the desired === and !== operators. When used, it generates a compile exception if the types don’t match, avoiding an unintended comparison of unrelated types that would always fail. However, the existing language specification will generate a compile error when using !==:

scala> 1 !== -1 || true
<console>:15: error: value || is not a member of Int
       1 !== -1 || true
                ^

Since !== ends in =, its’ precedence is the same as an assignment operator, so 1 !== -1 || true gets evaluated as 1 !== (-1 || true) by the Scala compiler.

By modifying the exceptions definition to “begins with”, this should get compiled with the desired precedence of (1 !== -1) || true.

Counter-Example

This can be worked around by using other operators:

scala> 1 =!= -1 || true
res0: Boolean = true

Implementation

    def isOpAssignmentName: Boolean = name match {
      case raw.NE | raw.LE | raw.GE | EMPTY =>
        false
      case name: SimpleName =>
        name.length > 0 && name.last == '=' && isOperatorPart(name.head) &&
        (name.length < 3 || !(name.startsWith(raw.NE) || name.startsWith(raw.GE) || name.startsWith(raw.LE))
      case _ =>
        false
    }

Drawbacks

This is a piece of the language specification that may have never been changed and has unintended consequences which are unknown.

The scope of the change would affect any custom assignment operators that use the patterns:

  • !=[.*]=
  • >=[.*]=
  • <=[.*]=

If there are DSLs or other implemented assignment operators using the above patterns, then the scala code would not compile without modifying the first = character to another.

Alternatives

As described in the counter-example, there are other operators available, and is not core functionality that is currently “broken” or has no alternative.

References

  1. Scala Specification
  2. TypeSafeEquals
3 Likes

Scala 3 shouldn’t change anything here, but what you’re suggesting sounds reasonable, I would encourage to write a SIP for it.

Overall, I think it’s an interesting idea.

From the marketing perspective, it might be good to call this “Relational Operator Clause” instead of “Assignment Operator Precedence Exceptions”, so we can discuss what constitutes relational (or comparison) operators.

The current exception clause:

… that ends in an equals character “ = ”, with the exception of operators for which one of the following conditions holds:

  1. the operator also starts with an equals character, or
  2. the operator is one of (<=) , (>=) , (!=) .

is effectively defining the relational operators, and you’re proposing to expand that to the following:

… that ends in an equals character “ = ”, with the exception of relational operators for which the following condition holds:

  • the operator also starts with one of =, <= , >= , != .

Given Scala 3 is adding some sort of Eq typeclass with its multiversal equality, I am not sure about the committee’s appetite to accommodate DSL in this area.

I am extremely wary of any change to the precedence rules of any language. This is a recipe for silent breakages in codebases that we know nothing about, for no real reason. And by no real reason, I mean that yes, sure, you can write a long post explaining why it should be the way you suggest, but I can always write another post explaining why it should be another way, and there is never any objective measure we can make. So when in doubt, apply the motto “If it ain’t broke, don’t fix it”.

5 Likes

I like @eed3si9ns generalization of the topic, I think that makes sense. Regarding the “counter examples” argument @sjrd, I guess that’s the part I’m struggling with. Is there an argument/“another long post” on why “<==” SHOULD be an assignment operator? Or is this more closely to an oversight?

IIRC, was there some type of community repository of popular source code that I could use to find counter-examples? Could anyone point me in that direction?

Whether such a post already exists or not is beside the point. The point is that it could exist. Also the fact that it does not exist says nothing at this point, because it already is considered an assignment operator, so someone who wants that to be the correct behavior is simply using it, not writing posts praising how the existing precedence is good.

I think a better question will be why do we need === (and the rest) and not just use == (and the rest)?
I know there are issues with ==, but they can be resolved if we remove it from Any and implement it using extension methods. See the following proposal:

@sjrd I ran into this issue again (I really want to use !== to match javascript/php) have your thoughts changed on whether there would be a chance in the proverbial hell for this rule change to even be considered by the SIP committee?

Upon reflection, this isn’t actually true. The above example 1 !== -1 || true requires existing code to be written as (1 !== -1) || true to compile, so changing the precedence here wouldn’t change existing code.

There would have to be a very obtuse example that would break, and I’m unable to provide an example.

I think it would be easy to do the change in two steps:

  • first, detect current usage of operators that would have their meaning changed if the new precedence rule was applied, and issue a deprecation/migration warning (and provide a rewrite rule that inserts parentheses to keep the same behavior as before)
  • in the next major release, change the precedence rule to the new behavior.
1 Like

I don’t see much point to the idea. You can already do =<=, =>= and =!= if you need alternatives.

And you might possibly break something, and/or cause people trouble for basically no reason.

The motivating example doesn’t explain why you can’t follow the existing rules, so I don’t think the change is adequately motivated.

For instance, I could propose that +*+ should have the precedence of * not the precedence of +, but if I were to make such a proposal I would need to explain why I couldn’t spell it *+* or *+ or somesuch.

2 Likes

Part of the reason is for language accessibility (e.g. javascript/php/etc commonly use !== and ===). When you start getting into =!= or *> you start losing people.

I think @eed3si9n provided some good insight, it’s not about “assignment operator precedence” but perhaps it should be thought of as “relational operators” and their precedence in the language.

I added a draft PR here: https://github.com/scala/scala/pull/9104 would love to get a community build ran against it

1 Like

Initial community build shows no failures with this change: https://github.com/scala/scala/pull/9104#issuecomment-655667922

1 Like

Since I guess few people read PRs compared to this thread, I’ll repeat myself here:

IMO using the community build for this is a dangerous thing to do. The community build is not there to say, hey I want to intentionally break this thing, let me see if I can get away with it. It’s there to say, I’ve got to make potentially far reaching internal changes in the compiler, perhaps to implement a new feature or fix a bug, let me get some more confidence that I’m not breaking anything by mistake. In other words, it’s a regression suite.

If it were the former, we’d need to put every possible library (and application) in it. But that’s a clearly spelled out non goal of the community build.

2 Likes

I understand your point of view.

The name Scala stands for “scalable language.” The language is so named because it was designed to grow with the demands of its users.

For the language whose methods can automatically invoked with infix syntax, not having the definition for comparison operator is an oversight IMO.

The community build is just one potential step in implementation, but I think the underlying discussion is worth having.

I think people are smarter than that.

If not, we’d better remove [T] and make it be <T> like everyone else does. That’s got to lose a lot more people.

The proposal also doesn’t clearly pick out things that look intuitively like relational operators to me. <=/= is clearly a crossed-out arrow, not a special kind of less-than. Of course we can learn the new rules, but I still think the justification is lacking.

Anyway, I don’t think having equality precedence instead of relational precedence is such a big deal. Yes, maybe you need parens if you’re comparing your booleans to each other, but people usually don’t.

8 Likes