可匹配特質
新的特質 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
和通用相等性
在開啟 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
表示在存在抽象類型和不透明類型的情況下,通用相等是不安全的,因為它無法正確區分類型的含義和其表示。由於 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)
轉換成類型錯誤來緩解這個問題。