在 GitHub 上編輯此頁面

可匹配特質

新的特質 Matchable 控制樣式比對的能力。

問題

Scala 3 標準函式庫有一個類型 IArray,用於不可變陣列,定義如下

opaque type IArray[+T] = Array[_ <: T]

IArray 類型提供 lengthapply 的擴充方法,但沒有提供 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。它由 AnyValAnyRef 延伸。由於 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 目前是一個標記特質,沒有任何方法。隨著時間的推移,我們可能會將 getClassisInstanceOf 方法遷移到其中,因為這些方法與模式配對密切相關。

Matchable 和通用相等性

在開啟 Matchable 警告後,對類型為 Any 的選擇器進行模式配對的方法將需要強制轉換。最常見的此類方法是通用 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 表示在存在抽象類型和不透明類型的情況下,通用相等是不安全的,因為它無法正確區分類型的含義和其表示。由於 AnyMatchable 都會消除為 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)

轉換成類型錯誤來緩解這個問題。