類型測試
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)
將會未檢查。