Pre-SIP: relaxed `strictEquality` for `==` and `!=`

Recently, SIP-67 was implemented and released in Scala 3.8. I think it does a fantastic job of what it set out to do:

This proposal aims to make the strictEquality feature easier to adopt by making pattern matching against singleton cases (e. g. Nil or None) work even when the relevant sealed or enum type does not (or cannot) have a derives CanEqual clause.

I enabled strictEquality on a codebase where it wasn’t previously set, compiled (to a bunch of new errors), enabled strictEqualityPatternMatching, and saw the number of compile errors drastically reduced.

In the remaining errors, I see an opportunity; can we apply similar rules to == and !=?

Using the example from the previous SIP:

import scala.language.strictEquality

enum Nat:
  case Zero
  case Succ(n: Nat)

  def +(that: Nat): Nat =
    this match
      case Nat.Zero => that
      case Nat.Succ(x) => Nat.Succ(x + that)

Suppose I only care about Zero and don’t care about other cases:

  def +(that: Nat): Int =
    this match
      case Nat.Zero => that
      case _ => ???

Now, since I only care about the case object, I feel like I should be able to rewrite it as the equivalent (I think?):

  def +(that: Nat): Int =
    if this == Nat.Zero then that
    else ???

However, that produces the same

[error] 41 |    if this == Nat.Zero then that
[error]    |       ^^^^^^^^^^^^^^^^
[error]    |Values of Nat and Nat cannot be compared with == or !=
[error] one error found

that the SIP example code produces without strictEqualityPatternMatching:

[error] ./nat.scala:9:10
[error] Values of types Nat and Nat cannot be compared with == or !=
[error]     case Nat.Zero => r
[error]          ^^^^^^^^

Here are a few more examples, taken from the codebase I’m working on:

In a collection of ZIO Json objects, find the first one that isn’t a null json value and do something with it:

[error] 142 |        sources.find(_ != Json.Null) match {
[error]     |                     ^^^^^^^^^^^^^^
[error]     |Values of types zio.json.ast.Json and object zio.json.ast.Json.Null cannot be compared with == or !=

Have a helper method that determines if an app is in dev mode:

sealed trait Mode
case object Dev  extends Mode
case object Prod extends Mode

final case class AppModeConfig(mode: Mode, layers: Map[String, Mode]) {
  def isDev: Boolean = mode == Dev
}
[error] 36 |  def isDev: Boolean = mode == Dev
[error]    |                       ^^^^^^^^^^^
[error]    |Values of types com.connectstrata.libs.core.Mode and object com.connectstrata.libs.core.Dev cannot be compared with == or !=

I’m aware that in both cases, I can avoid manual CanEqual instances now by using pattern matching with the new language flag (and in the case of Mode, I can trivially derive CanEqual, or add an isDev method to the trait and implement it in each subclass), but these are still extra hurdles to adopting strictEquality (which I think is a great feature aside from how tedious it is to use, especially in large pre-existing codebases).

1 Like