在 GitHub 上编辑此页面

类型测试

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) 将是不受检查的。