and I have tried to follow the examples, but I just can’t get it to work. For example, I have
override def equals(that: Any): Bool = that match {
case that: Scalar => num == that.num && (num == 0 || units == that.units)
case that: Real => this == Scalar(that)
case that: Int => this == Scalar(that)
case _ => throw new RuntimeException(s"$this cannot = $that")
}
given CanEqual[Scalar, Scalar] = CanEqual.derived
given CanEqual[Scalar, Real] = CanEqual.derived
given CanEqual[Scalar, Int] = CanEqual.derived
But I still get many errors like this:
[error] 68 | if (det != 0) { // check for intersection
[error] | ^^^^^^^^
[error] | Values of types scalar_.Scalar and Int cannot be compared with == or !=
I have tried with and without
import scala.language.strictEquality
Any ideas about what the problem might be? Thanks.
I have no experience with Scala 3 at all, yet, but from what I see I’d guess this isn’t possible.
The intent of CanEqual seems to be to disallow equality comparisons between different types (e.g. Scalar/Int). It only “accidentally” has two type parameters - conceptually it’s intended to be only one, and the two that are there for pragmatic reasons are expected to have an “isAssignable” relationship.
It looks like you cannot have a meaningful CanEqual[Int, Scalar] instance, since you cannot define this logic in Scalar#equals() and CanEqual is sealed on purpose, so you can only derive instances, but not define any from scratch.
So CanEqual[Scalar, Int] just seems to be a “leak” in the intended type safety. But my understanding may be wrong…
scala> Scalar(1) == 3
1 |Scalar(1) == 3
|^^^^^^^^^^^^^^
|Values of types Scalar and Int cannot be compared with == or !=
scala> given CanEqual[Scalar, Int] = CanEqual.derived
lazy val given_CanEqual_Scalar_Int: CanEqual[Scalar, Int]
scala> Scalar(1) == 3
val res0: Boolean = false
Did you define given CanEqual[Scalar, Int] in the implicit scope (e.g. in the companion object) of Scalar?
Yes, for U >: T, i.e. with an “isAssignable” relationship. And it seems possible to implement CanEqual[T, U] and CanEqual[U, T] for arbitrary reference types T and U if you control their #equals() implementation. But I don’t see how to implement a semantically sound CanEqual[Int, Scalar].
However, maybe I read too much into the question and the OP doesn’t require the symmetry property.
True, in this case you can’t/shouldn’t define CanEqual[Int, Scalar] which means the equality relationship isn’t reflexive. However the compiler does seem to allow non-reflexive CanEqual[Scalar, Int] to work. Whether that’s a good idea is a different question…
Thanks for the suggestions. I had the “given CanEqual” statement in the wrong place (inside the class constructor). After I moved it to the enclosing package object, the compiler suggested that I use
import scalar_.given_CanEqual_Scalar_Int
When I added that import statement to a client file, I was able to test equality between a Scalar and an Int.
However, that seems like an arbitrary name, and it applies only to the one file. I would have to add this import statement to every file in which I want that functionality, and that is onerous. I didn’t have to do that in Scala 2. I hope there is a better way.
My Scalar class is currently in a package object. I tried putting the “given” in the package object outside of the class definition, but that didn’t work. Shouldn’t that have the same effect as putting it in the companion object? If not, I guess I will have to break the package object up into a class and companion object.