可匹配性特质
一个新的特性 Matchable 控制模式匹配的能力。
问题
Scala 3 标准库有一个类型 IArray 用于不可变数组,定义如下
opaque type IArray[+T] = Array[_ <: T]
IArray 类型提供 length 和 apply 的扩展方法,但不提供 update;因此,似乎类型为 IArray 的值无法更新。
但是,由于模式匹配,存在一个潜在的漏洞。考虑以下情况
val imm: IArray[Int] = ...
imm match
  case a: Array[Int] => a(0) = 1
测试将在运行时成功,因为 IArray 在运行时表示为 Array。但是,如果我们允许这样做,它将破坏不可变数组的基本抽象。
旁注:也可以通过强制转换来实现相同的效果
imm.asInstanceOf[Array[Int]](0) = 1
但这并不是一个大问题,因为在 Scala 中,asInstanceOf 被理解为低级且不安全的。相比之下,在没有警告或错误的情况下编译的模式匹配不应该破坏抽象。
还要注意,问题并不局限于 不透明类型 作为模式选择器。以下使用参数类型 T 的值作为模式选择器的轻微变体会导致相同的问题
def f[T](x: T) = x match
  case a: Array[Int] => a(0) = 0
f(imm)
最后,请注意,问题并不仅仅与 不透明类型 相关。任何无界类型参数或抽象类型都不应该通过模式匹配进行分解。
解决方案
现在有一种新的类型 scala.Matchable 来控制模式匹配。当对构造函数模式 C(...) 或类型模式 _: C 进行模式匹配时,要求选择器类型符合 Matchable。如果情况并非如此,则会发出警告。例如,当编译本节开头示例时,我们会得到
> sc ../new/test.scala -source future
-- Warning: ../new/test.scala:4:12 ---------------------------------------------
4 |    case a: Array[Int] => a(0) = 0
  |            ^^^^^^^^^^
  |            pattern selector should be an instance of Matchable,
  |            but it has unmatchable type IArray[Int] instead
为了允许从 Scala 2 迁移以及在 Scala 2 和 3 之间进行交叉编译,该警告仅在 -source future-migration 或更高版本中启用。
Matchable 是一个通用特征,其父类为 Any。它由 AnyVal 和 AnyRef 扩展。由于 Matchable 是每个具体值或引用类的超类型,这意味着可以像以前一样匹配此类类的实例。但是,以下类型的匹配选择器将产生警告
- 类型 Any:如果需要模式匹配,则应使用Matchable代替。
- 无界类型参数和抽象类型:如果需要模式匹配,它们应该具有一个上界 Matchable。
- 仅受某些通用特征约束的类型参数和抽象类型:同样,应将 Matchable添加为约束。
以下是顶级类和特征及其定义方法的层次结构
abstract class Any:
  def getClass
  def isInstanceOf
  def asInstanceOf
  def ==
  def !=
  def ##
  def equals
  def hashCode
  def toString
trait Matchable extends Any
class AnyVal extends Any, Matchable
class Object extends Any, Matchable
Matchable 目前是一个没有方法的标记特征。随着时间的推移,我们可能会将 getClass 和 isInstanceOf 方法迁移到它,因为这些方法与模式匹配密切相关。
Matchable 和通用相等性
    对类型为 Any 的选择器进行模式匹配的方法,一旦启用 Matchable 警告,就需要进行强制转换。最常见的此类方法是通用 equals 方法。它必须像以下示例中那样编写
class C(val x: String):
  override def equals(that: Any): Boolean =
    that.asInstanceOf[Matchable] match
      case that: C => this.x == that.x
      case _ => false
将that强制转换为Matchable表明,在存在抽象类型和不透明类型的情况下,通用相等性是不安全的,因为它无法正确区分类型的含义与其表示。由于Any和Matchable都擦除为Object,因此强制转换在运行时保证成功。
例如,考虑以下定义
opaque type Meter = Double
def Meter(x: Double): Meter = x
opaque type Second = Double
def Second(x: Double): Second = x
这里,通用equals将对以下情况返回true
Meter(10).equals(Second(10))
尽管从数学角度来看这显然是错误的。使用多重宇宙相等性,可以通过将
import scala.language.strictEquality
  Meter(10) == Second(10)
转换为类型错误来在一定程度上缓解这个问题。