Scala 3 strictEquality模式下类型参数相等性比较的疑问
Scala 3 strictEquality 模式下的相等性比较问题解答
问题背景
启用-language:strictEquality后,以下代码会触发编译错误:
trait X[A]: def val1: A def val2: A def f() = if val1 == val2 then println("Same")
错误提示:
Values of types A and A cannot be compared with == or !=
添加以下代码可解决该问题:
given CanEqual[A, A] = CanEqual.derived
疑问解答
1. 为何相同类型的实例无法进行相等性比较?哪些A类型会导致val1 == val2不合法?
在strictEquality模式下,Scala不再默认允许任意类型使用==/!=,而是要求存在隐式的CanEqual[L, R]实例,以此证明左右类型的相等比较是安全且符合预期的。
对于类型参数A,编译器无法提前确定它的具体类型:
- 如果
A是未定义明确相等语义的抽象类型(比如未实现equals的特质),直接比较可能得到非预期的结果; - 如果
A是Scala默认不提供CanEqual实例的类型(比如Function1等函数类型,因为函数的引用相等通常不是业务逻辑需要的相等判断); - 甚至是自定义的未正确重写
equals/hashCode的类,误用==会触发引用相等判断,而非业务期望的内容相等。
因此编译器在严格模式下不会默认给A生成CanEqual[A, A]实例,必须由开发者显式声明。
2. 是否存在可给类型添加==、!=操作的特质?
存在,Scala 3提供了scala.Equals特质:
- 任何类型只要继承
Equals,就需要实现equals和hashCode方法,编译器会自动为其生成对应的CanEqual实例,从而支持==/!=操作; - 此外,也可以像问题中的解决方案那样,手动为类型提供
CanEqual隐式实例,直接开启相等比较能力。
3. 编译器无法推断类型参数A可与自身比较,为何上述given语句能解决问题?CanEqual.derived的作用是什么,为何默认不自动启用?
given CanEqual[A, A] = CanEqual.derived是显式为类型参数A提供了一个自身到自身的CanEqual实例,明确告诉编译器:A类型的实例可以安全地互相比较。CanEqual.derived是一个推导方法,它会在当前上下文里自动寻找或生成合适的CanEqual实例:如果A是Equals的子类、基本类型、字符串、标准集合等已有默认CanEqual实现的类型,derived会直接复用这些实例;如果A是自定义类型,也会基于其equals方法生成对应的实例。- 之所以默认不自动启用
CanEqual.derived,是因为strictEquality的设计目标就是避免不安全的相等比较:Scala 2中默认允许所有类型用==,经常出现开发者误将引用相等当成内容相等的情况(比如函数实例、自定义类未重写equals时)。强制显式声明CanEqual实例,是让开发者明确确认该类型的相等比较逻辑是符合预期的,从根源减少bug。
内容的提问来源于stack exchange,提问作者Victor Grigoriu
相关产品推荐
相关产品推荐

