类型测试
TypeTest
在模式匹配中,有两种情况需要执行运行时类型测试。第一种情况是使用 ascription 模式表示法进行显式类型测试。
(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)
将是不受检查的。