![](/img/trans.png)
[英]Scala compiler says “No TypeTag available for T” in method using generics
[英]Typesafe generics using TypeTag (with safe cast)
我想实现参数类(例如List[T]
), x.cast[List[U]]
可以选择进行类型安全的转换(例如x.cast[List[U]]
)。
通过类型安全,我的意思是如果类型在运行时不正确,则强制类型转换可能引发异常,但是可以保证,如果强制类型转换成功,则结果值将为List[U]
类型。 (例如, asInstanceOf
不会执行此操作List(1,2,3).asInstanceOf[List[String]]
将成功,但返回不包含String
的List
。)
我的方法是使用TypeTag
标记所有应支持转换的TypeTag
。 具体来说,我将使用方法cast[U]
实现一个特征Typesafe
,该方法期望类型U
的隐式TypeTag
并在运行时检查类型是否为子类型。 这是我设法提供的代码:
import scala.reflect.runtime.universe.TypeTag
trait Typesafe[+T <: Typesafe[T]] {
val typeTag: TypeTag[_ <: T]
def cast[U](implicit typeTag: TypeTag[U]) = {
if (this.typeTag.tpe <:< typeTag.tpe)
this.asInstanceOf[U]
else
throw new ClassCastException(s"Cannot cast ${this.typeTag} to ${typeTag}")
}
}
逻辑是:继承Typesafe[T]
类T
必须使用TypeTag[T]
实例化typeTag
。 然后,仅当T
确实是U
的子类型时, cast[U]
的测试才能成功(否则, cast
的隐式参数不存在)。
我们可以按以下方式实现此特征(这是Set的一个简单包装器类):
class TypesafeSet[T](val set : Set[T])
(implicit val typeTag:TypeTag[_<:TypesafeSet[T]])
extends Typesafe[TypesafeSet[T]] {
}
子类型化有效,但是不幸的是,我们每次都需要指定extends Typesafe[...]
子句。
import scala.collection.immutable.ListSet
class TypesafeListSet[T](set: ListSet[T])
(implicit override val typeTag:TypeTag[_<:TypesafeListSet[T]])
extends TypesafeSet[T](set) with Typesafe[TypesafeListSet[T]] {
}
问题1:我们可以改进这种模式,以便不必重复extends Typesafe[...]
子句吗? (目前,如果我们不重复, TypesafeListSet[T]
不能被转换为TypesafeListSet[T]
但是,在下面的示例中,我们遇到了问题:
class TypesafeList[T](val list : List[T])
(implicit val typeTag:TypeTag[_<:TypesafeList[T]])
extends Typesafe[TypesafeList[T]] {
val self = this
def toSet : TypesafeSet[T] = new TypesafeListSet(ListSet(list : _*))
}
方法toSet
无法编译,因为编译器无法为new TypesafeListSet
解析隐式TypeTag[TypesafeListSet[T]]
。 需要从typeTag提取TypeTag[T]
,然后TypeTag[TypesafeListSet[T]]
重建TypeTag[TypesafeListSet[T]]
。 我不知道这怎么可能。
问题2:如何获取所需TypeTag
在toSet
? (一个选择是将类型TypeTag[TypesafeListSet[T]]
的隐式参数添加到toSet
,但这会向外推问题,并泄漏实现细节,即toSet
使用ListSet
。)
最后,可以编写以下代码,违反类型安全性:
class TypesafeOption[T](val option : Option[T])
(implicit val typeTag:TypeTag[_<:TypesafeList[T]])
extends Typesafe[TypesafeList[T]] {
}
在这里,我们用“不小心” TypesafeList
中的参数Typesafe
特性。 编译没有问题,但它意味着现在TypesafeOption
将有typeTag
的TypesafeList
! (因而在检查cast
将是不正确的,错误的类型转换可能会发生。)我认为,这种mixups可以很容易地发生,这将是很好的,如果他们可以通过编译器捕获。 (在一定程度上,所述类型约束T <: Typesafe[T]
已经避免这种mixups(以下此 ),但不幸的是不是一个在TypesafeOption
。)
问题3(已回答 ):我们是否可以改进特征Typesafe
的定义,以致无法以某种方式实例化Typesafe
从而使cast
行为不正确?
最后,几行代码应如何使用这些类:
import scala.reflect.runtime.universe.typeTag
object Test {
def main(args:Array[String]) = {
val list = new TypesafeList(List(1,2,3))
val set = list.toSet
val listSet : TypesafeListSet[Int] = set.cast[TypesafeListSet[Int]]
}
}
不幸的是,该代码无法编译。 编译器找不到用于调用new TypesafeList
的TypeTag
。 我们需要在该行中显式添加(typeTag[TypesafeList[Int]])
! (原因是new TypesafeList
期望使用TypeTag[_ <: TypesafeList[Int]]
,并且编译器不够聪明,无法看到他可以构造TypeTag[TypesafeList[Int]]
。)
问题4:我们如何定义TypesafeList
以便不需要显式地提供TypeTag
?
最后,我对整个示例有一些疑问:
问题5:运行时(至少)有两个不同的TypeTag
类,分别是scala.reflect.runtime.universe.TypeTag
和scala.reflect.api.TypeTags#TypeTag
。 这里哪一个是正确的?
问题6:我正在比较TypeTags
包含的类型(即typeTag.tpe
)。 我无视镜子。 也应该比较镜子吗? (换句话说,如果两个类型标签具有兼容的类型但具有不同的镜像,那么它们是否可以兼容分配?)
问题7 :(可能与问题6有关。)如果相同的合格名称的类型已由不同的类加载器加载,会发生什么情况? 在这种情况下,上面的代码正确吗? (据我所知,应该不可能将test.Test
从类加载器1加载到test.Test
从类加载器2加载。)
问题8( 回答 ): TypeTag
在这里是正确的工具吗? 还是应该直接用Type
标记对象? (毕竟,我只比较cast
的类型。)但是据我所见(从各种讨论中), TypeTag
提出来是为类型安全标记类问题的解决方案。 为什么? 还是TypeTag
用于什么?
问题9:对此有何评论? 在运行时比较两种类型(使用<:<
)听起来很昂贵……是否有替代方法? (我想过可能从TypeTags
到Boolean
的映射构造一个映射,该映射记住哪些类型是赋值兼容的。但是,这样的查找会更快吗?据我所知, TypeTag
没有用于快速查找的唯一ID。 (我认为,GHC为此使用了“指纹”。)
问题10:还有其他意见吗? 我做错了什么? 我的结论是, cast
是类型安全的正确吗?
编辑:此解决方案是错误的。 使用此解决方案时,仍然可以使用val typeTag = typeTag[Nothing]
,然后将该类转换为任何类型。
对问题3的答案 ( 我们是否可以改进特征Typesafe
的定义,以致无法以某种方式实例化Typesafe
从而Typesafe
行为不正确? )
这可以通过指定“自我类型”(请参见spec )来完成。 在特征中,可以指定继承该类的类应具有的类型T
这是通过在特征定义的开头编写this:T =>
来完成的。 在我们的特定情况下:
trait Typesafe[+T <: Typesafe[T]] {
this : T =>
val typeTag: TypeTag[_ <: T]
def cast[U](implicit typeTag: TypeTag[U]) = {
if (this.typeTag.tpe <:< typeTag.tpe)
this.asInstanceOf[U]
else
throw new ClassCastException(s"Cannot cast ${this.typeTag} to ${typeTag}")
}
}
现在,任何扩展Typesafe[T]
必须为T
类型。 也就是说,如果T
是X
的超类型,则只能编写class X extends Typesafe[T]
,因此将保证提供给Typesafe
的类型标记是X
的超类型。
问题8的答案 (TypeTag是这里的正确工具吗?还是应该直接用Types标记对象?) :
一个原因是TypeTag
具有一个类型参数,该参数表示被标记的类型。 也就是说, TypeTag[T]
静态地强制我们为T
提供一个TypeTag
。 如果我们改用Type
,则不会强制Typesafe.typeTag
值是正确类型的标签。
例:
trait Typesafe[+T <: Typesafe[T]] {
val typ: Type
[...]
}
现在可以写:
class TypesafeSet[T](val set : Set[T])
extends Typesafe[TypesafeSet[T]] {
val typ = typeOf[String] // !!!
}
没有TypeTag
似乎无法避免这种情况。
(但是,这不能解释为什么TypeTag
除了类型之外还需要具有一个镜像)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.