TypeTest
在模式匹配时,有两种情况必须执行运行时类型测试。 第一种情况是使用 ascription pattern 符号的显式类型测试。
(x: X) match {
case y: Y =>
}
第二种情况是提取器接受的参数不是 scrutinee 类型的子类型。
(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]
}
It provides an extractor that returns its argument typed as a T
if the argument is a T
. It can be used to encode a type test.
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
}
To avoid the syntactic overhead the compiler will look for a type test automatically if it detects that the type test is on abstract types. This means that x: Y
is transformed to tt(x)
and x @ Y(_)
to tt(x @ Y(_))
if there is a contextual TypeTest[X, Y]
in scope. The previous code is equivalent to
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
}
We could create a type test at call site where the type test can be performed with runtime class tests directly as follows
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)
The compiler will synthesize a new instance of a type test if none is found in scope as:
new TypeTest[A, B] {
def unapply(s: A): Option[s.type & B] = s match {
case s: B => Some(s)
case _ => None
}
}
If the type tests cannot be done there will be an unchecked warning that will be raised on the case s: B =>
test.
The most common TypeTest
instances are the ones that take any parameters (i.e. TypeTest[Any, T]
). To make it possible to use such instances directly in context bounds we provide the alias
package scala.reflect
type Typeable[T] = TypeTest[Any, T]
This alias can be used as
def f[T: Typeable]: Boolean =
"abc" match {
case x: T => true
case _ => false
}
f[String] // true
f[Int] // false
TypeTest and ClassTag
TypeTest
is a replacement for functionality provided previously by ClassTag.unapply
. Using ClassTag
instances was unsound since classtags can check only the class component of a type. TypeTest
fixes that unsoundness. ClassTag
type tests are still supported but a warning will be emitted after 3.0.
Example
Given the following abstract definition of Peano numbers that provides two given instances of types TypeTest[Nat, Zero]
and 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]
}
together with an implementation of Peano numbers based on type Int
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) Some(x) else None
}
def typeTestOfSucc: TypeTest[Nat, Succ] = new {
def unapply(x: Nat): Option[x.type & Succ] =
if (x > 0) Some(x) else None
}
}
it is possible to write the following program
@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"
}
Note that without the TypeTest[Nat, Succ]
the pattern Succ.unapply(nat: Succ)
would be unchecked.