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
strictEqualityfeature easier to adopt by making pattern matching against singleton cases (e. g.NilorNone) work even when the relevantsealedorenumtype does not (or cannot) have aderives CanEqualclause.
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).