在 GitHub 上編輯此頁面

類型測試

TypeTest

在模式配對時,有兩種情況必須執行執行時期類型測試。第一個情況是使用歸屬模式表示法進行明確類型測試。

(x: X) match
  case y: Y =>

第二個情況是當萃取器取得的參數不是受檢視類型的一個子類型時。

(x: X) match
  case y @ Y(n) =>

object Y:
  def unapply(x: Y): Some[Int] = ...

在這兩種情況下,將在執行時期執行類別測試。但是,當類型測試在抽象類型(類型參數或類型成員)上時,無法執行測試,因為類型在執行時期會被刪除。

可以提供 TypeTest 來使此測試成為可能。

package scala.reflect

trait TypeTest[-S, T]:
  def unapply(s: S): Option[s.type & T]

它提供一個萃取器,如果參數為 T,則會傳回其參數,並將其類型設定為 T。它可用於編碼類型測試。

def f[X, Y](x: X)(using tt: TypeTest[X, Y]): Option[Y] = x match
  case tt(x @ Y(1)) => Some(x)
  case tt(x) => Some(x)
  case _ => None

為了避免語法開銷,如果編譯器偵測到類型測試在抽象類型上,它會自動尋找類型測試。這表示如果範圍內有情境 TypeTest[X, Y],則 x: Y 會轉換為 tt(x),而 x @ Y(_) 會轉換為 tt(x @ Y(_))。先前的程式碼等同於

def f[X, Y](x: X)(using TypeTest[X, Y]): Option[Y] = x match
  case x @ Y(1) => Some(x)
  case x: Y => Some(x)
  case _ => None

我們可以在呼叫位置建立類型測試,類型測試可以使用執行時期類別測試直接執行,如下所示

val tt: TypeTest[Any, String] =
  new TypeTest[Any, String]:
    def unapply(s: Any): Option[s.type & String] = s match
      case s: String => Some(s)
      case _ => None

f[AnyRef, String]("acb")(using tt)

如果範圍內找不到類型測試,編譯器會合成一個類型測試的新執行個體,如下所示

new TypeTest[A, B]:
  def unapply(s: A): Option[s.type & B] = s match
    case s: B => Some(s)
    case _ => None

如果無法執行類型測試,將會在 case s: B => 測試中產生未檢查的警告。

最常見的 TypeTest 執行個體是不帶任何參數的 (即 TypeTest[Any, T])。為讓此類執行個體可直接用於內容限制中,我們提供別名

package scala.reflect

type Typeable[T] = TypeTest[Any, T]

此別名可用於

def f[T: Typeable]: Boolean =
  "abc" match
    case x: T => true
    case _ => false

f[String] // true
f[Int] // false

TypeTest 和 ClassTag

TypeTest 是先前由 ClassTag.unapply 提供的功能的替代方案。使用 ClassTag 執行個體是不健全的,因為類別標籤只能檢查類型的類別組成部分。 TypeTest 修復了這種不健全性。 ClassTag 類型測試仍受支援,但 3.0 之後會發出警告。

範例

假設有以下 Peano 數字的抽象定義,其中提供了 TypeTest[Nat, Zero]TypeTest[Nat, Succ] 類型的兩個給定執行個體

import scala.reflect.*

trait Peano:
  type Nat
  type Zero <: Nat
  type Succ <: Nat

  def safeDiv(m: Nat, n: Succ): (Nat, Nat)

  val Zero: Zero

  val Succ: SuccExtractor
  trait SuccExtractor:
    def apply(nat: Nat): Succ
    def unapply(succ: Succ): Some[Nat]

  given typeTestOfZero: TypeTest[Nat, Zero]
  given typeTestOfSucc: TypeTest[Nat, Succ]

以及基於類型 Int 的 Peano 數字實作

object PeanoInt extends Peano:
  type Nat  = Int
  type Zero = Int
  type Succ = Int

  def safeDiv(m: Nat, n: Succ): (Nat, Nat) = (m / n, m % n)

  val Zero: Zero = 0

  val Succ: SuccExtractor = new:
    def apply(nat: Nat): Succ = nat + 1
    def unapply(succ: Succ) = Some(succ - 1)

  def typeTestOfZero: TypeTest[Nat, Zero] = new:
    def unapply(x: Nat): Option[x.type & Zero] =
      if x == 0 then Some(x) else None

  def typeTestOfSucc: TypeTest[Nat, Succ] = new:
    def unapply(x: Nat): Option[x.type & Succ] =
      if x > 0 then Some(x) else None

可以撰寫以下程式

@main def test =
  import PeanoInt.*

  def divOpt(m: Nat, n: Nat): Option[(Nat, Nat)] =
    n match
      case Zero => None
      case s @ Succ(_) => Some(safeDiv(m, s))

  val two = Succ(Succ(Zero))
  val five = Succ(Succ(Succ(two)))

  println(divOpt(five, two))  // prints "Some((2,1))"
  println(divOpt(two, five))  // prints "Some((0,2))"
  println(divOpt(two, Zero))  // prints "None"

請注意,如果沒有 TypeTest[Nat, Succ],模式 Succ.unapply(nat: Succ) 將會未檢查。