这是一个简单的例子:
object MatchErasedType {
trait Supe {
self: Singleton =>
type T1
lazy val default: T1
def process(v: Any): T1 = {
v match {
case vv: T1 => vv
case _ => default
}
}
}
}
它会抛出编译器警告:
MatchErasedType.scala:13:14: the type test for Supe.this.T1 cannot be checked at runtime
这个例子很有趣,因为
Supe
的任何实例都保证是 Singleton
。因此 process
的延迟具体化不会有任何擦除。因此,这个问题实际上涉及两种不同的情况:
如果
Supe
的所有实例都是特化的Singleton
,大概不使用任何隐式召唤或转换,如何消除它?
其他情况如何排除?
更新1:应该注意的是,
v: Any
的类型在编译时无法得知,调用站点将无法提供此类信息。从今以后,这个问题不适用于 T1 无法解析为具体类和/或运行时条件的情况。
在 Scala 2 中
trait Supe { self: Singleton =>
type T1 <: Singleton
def default: T1
def process(v: Any): T1 =
v match {
case vv: T1 => println(1); vv
case _ => println(2); default
}
}
object Impl1 extends Supe {
override type T1 = Impl1.type
override lazy val default: T1 = this
}
object Impl2 extends Supe {
override type T1 = Impl2.type
override lazy val default: T1 = this
}
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 1
为了克服类型擦除,在 Scala 2 中我们使用
scala.reflect.ClassTag
。要么(方法 1)
trait Supe { self: Singleton =>
type T1 <: Singleton
def default: T1
def process(v: Any)(implicit ct: ClassTag[T1]): T1 =
v match {
case vv: T1 => println(1); vv
case _ => println(2); default
}
}
object Impl1 extends Supe {
override type T1 = Impl1.type
override lazy val default: T1 = this
}
object Impl2 extends Supe {
override type T1 = Impl2.type
override lazy val default: T1 = this
}
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2
或(方法 2)
trait Supe { self: Singleton =>
type T1 <: Singleton
def default: T1
implicit def ctag: ClassTag[T1]
def process(v: Any): T1 =
v match {
case vv: T1 => println(1); vv
case _ => println(2); default
}
}
object Impl1 extends Supe {
override type T1 = Impl1.type
override lazy val default: T1 = this
override implicit def ctag: ClassTag[T1] = {
val ctag = null // hiding implicit by name, otherwise implicitly[...] returns the above ctag
implicitly[ClassTag[T1]]
}
}
object Impl2 extends Supe {
override type T1 = Impl2.type
override lazy val default: T1 = this
override implicit def ctag: ClassTag[T1] = {
val ctag = null // hiding implicit by name, otherwise implicitly[...] returns the above ctag
implicitly[ClassTag[T1]]
}
}
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2
同样,在 Scala 3 中
trait Supe { self: Singleton =>
type T1 <: Singleton
lazy val default: T1
def process(v: Any): T1 =
v match {
case vv: T1 => println(1); vv
case _ => println(2); default
}
}
object Impl1 extends Supe {
override type T1 = Impl1.type
override lazy val default: T1 = this
}
object Impl2 extends Supe {
override type T1 = Impl2.type
override lazy val default: T1 = this
}
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 1
在 Scala 3 中,使用
scala.reflect.Typeable
(scala.reflect.TypeTest
) 代替 ClassTag
。方法1有效
trait Supe { self: Singleton =>
type T1 <: Singleton
lazy val default: T1
def process(v: Any)(using Typeable[T1]): T1 =
v match {
case vv: T1 => println(1); vv
case _ => println(2); default
}
}
object Impl1 extends Supe {
override type T1 = Impl1.type
override lazy val default: T1 = this
}
object Impl2 extends Supe {
override type T1 = Impl2.type
override lazy val default: T1 = this
}
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2
关于方法2,在Scala 3中没有办法通过名称隐藏隐式,所以我们将其放入本地作用域中,这样就不会在对象隐式作用域中找到它
trait Supe { self: Singleton =>
type T1 <: Singleton
lazy val default: T1
def tt: Typeable[T1]
def process(v: Any): T1 = {
given Typeable[T1] = tt
v match {
case vv: T1 => println(1); vv
case _ => println(2); default
}
}
}
object Impl1 extends Supe {
override type T1 = Impl1.type
override lazy val default: T1 = this
override def tt: Typeable[T1] = summon
}
object Impl2 extends Supe {
override type T1 = Impl2.type
override lazy val default: T1 = this
override def tt: Typeable[T1] = summon
}
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2
我仍然认为
或given
在所有情况下都可以避免。这两种方法都需要大量样板,特别是当模式匹配包含大量并集或交集类型时using
在上一个示例中,使用
或T1 with Int
等类型添加每个新案例需要 Typeable。这在很多情况下是不可行的。T1 {def v: Int}
ClassTag
、TypeTag
、shapeless.Typeable
/TypeCase
(Scala 2)、TypeTest
/Typeable
、Shapeless-3 Typeable
(Scala 3) 是标准工具克服类型擦除。运行时匹配类型对于模式匹配来说并不典型。如果您的业务逻辑基于类型,那么也许您根本不需要模式匹配,也许类型类会是更好的选择
trait Supe:
self: Singleton =>
type T1 <: Singleton
lazy val default: T1
trait Process[A]:
def process(a: A): T1
trait LowPriorityProcess:
given low[A]: Process[A] with
def process(a: A): T1 = { println(2); default }
object Process extends LowPriorityProcess:
given [A <: T1]: Process[A] with
def process(a: A): T1 = { println(1); a }
def process[A: Process](v: A): T1 =
summon[Process[A]].process(v)
object Impl1 extends Supe:
override type T1 = Impl1.type
override lazy val default: T1 = this
object Impl2 extends Supe:
override type T1 = Impl2.type
override lazy val default: T1 = this
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2
或
trait Process[A, T1 <: Singleton]:
def process(a: A, default: T1): T1
trait LowPriorityProcess:
given low[A, T1 <: Singleton]: Process[A, T1] with
def process(a: A, default: T1): T1 =
{println(2); default}
object Process extends LowPriorityProcess:
given[A <: T1, T1 <: Singleton]: Process[A, T1] with
def process(a: A, default: T1): T1 =
{println(1); a }
trait Supe:
self: Singleton =>
type T1 <: Singleton
lazy val default: T1
def process[A](v: A)(using p: Process[A, T1]): T1 =
p.process(v, default)
object Impl1 extends Supe:
override type T1 = Impl1.type
override lazy val default: T1 = this
object Impl2 extends Supe:
override type T1 = Impl2.type
override lazy val default: T1 = this
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2
使用
或T1 with Int
等类型添加每个新案例需要T1 {def v: Int}
。这在很多情况下是不可行的。Typeable
trait Supe:
self: Singleton =>
type T1 <: Singleton
lazy val default: T1
def tt[S <: T1]: Typeable[S]
def process(v: Any): T1 =
given Typeable[T1] = tt
given tt1: Typeable[T1 with SomeTrait] = tt
given tt2: Typeable[T1 {def v: Int}] = tt
//...
v match
case vv: T1 => {println(1); vv}
case _ => {println(2); default}
trait SomeTrait
object Impl1 extends Supe:
override type T1 = Impl1.type with SomeTrait
override lazy val default: T1 = this.asInstanceOf[T1]
override def tt[S <: T1]: Typeable[S] = summon[Typeable[S @unchecked]]
object Impl2 extends Supe:
override type T1 = Impl2.type {def v: Int}
override lazy val default: T1 = this.asInstanceOf[T1]
override def tt[S <: T1]: Typeable[S] = summon[Typeable[S @unchecked]]
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2