我想实现可以选择进行类型安全转换的参数类(如List[T]
)(例如x.cast[List[U]]
)。
通过类型安全我的意思是如果类型在运行时不正确,强制类型转换可能会抛出异常,但是如果转换成功,则保证结果值为List[U]
。 (例如,asInstanceOf
不会这样做。List(1,2,3).asInstanceOf[List[String]]
会成功,但会返回不包含List
的{{1}}。)
我的方法是使用String
标记应支持转换的所有对象。具体来说,我将使用方法TypeTag
实现特征Typesafe
,该方法期望类型为cast[U]
的隐式TypeTag
,并在运行时检查类型是否为子类型。这是我设法提出的代码:
U
逻辑是:继承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}")
}
}
的类T
必须使用Typesafe[T]
实例化typeTag
。然后TypeTag[T]
中的测试只有cast[U]
确实是T
的子类型才能成功(否则,U
的隐式参数不存在)。
我们可以按如下方式实现这个特性(这是一个简单的集合包装类):
cast
子类型工作,但不幸的是我们每次都需要指定class TypesafeSet[T](val set : Set[T])
(implicit val typeTag:TypeTag[_<:TypesafeSet[T]])
extends Typesafe[TypesafeSet[T]] {
}
子句。
extends Typesafe[...]
问题1:我们可以改进这种模式,以便我们不必重复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]] {
}
条款吗? (目前,如果我们不重复,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
的隐式TypeTag[TypesafeListSet[T]]
。需要从typeTag中提取new TypesafeListSet
,然后从中重构TypeTag[T]
。我不知道这是怎么回事。
问题2:如何在TypeTag[TypesafeListSet[T]]
中获取所需的TypeTag
? (一种选择是将toSet
类型的隐式参数添加到TypeTag[TypesafeListSet[T]]
,但这会将问题向外推,并泄漏实现细节,即toSet
使用toSet
}。)
最后,可以编写以下代码,违反了类型安全性:
ListSet
在这里,我们意外地&#34;&#34;在class TypesafeOption[T](val option : Option[T])
(implicit val typeTag:TypeTag[_<:TypesafeList[T]])
extends Typesafe[TypesafeList[T]] {
}
特征的参数中使用了TypesafeList
。这编译得很好,但这意味着现在Typesafe
TypesafeOption
将typeTag
! (因此,TypesafeList
中的检查将是不正确的,并且可能发生错误的转换。)我相信这样的混合可以很容易地发生,并且如果它们可以被编译器捕获将是好的。 (在某种程度上,类型约束cast
已经避免了这样的混淆(this之后),但遗憾的是T <: Typesafe[T]
中没有。)
问题3(answered):我们是否可以优化特征TypesafeOption
的定义,以便无法以某种方式实例化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]]
}
}
的{{1}}。我们需要在该行中明确添加TypeTag
! (原因是new TypesafeList
期望(typeTag[TypesafeList[Int]])
,并且编译器不够聪明,无法看到他只能构造new TypesafeList
。)
问题4:我们如何定义TypeTag[_ <: TypesafeList[Int]]
,以便不需要明确提供TypeTag[TypesafeList[Int]]
?
最后,我对整个例子有一些疑问:
问题5:运行时中至少有两个不同的TypesafeList
类,即TypeTag
和TypeTag
。哪一个在这里是正确的?
问题6:我正在比较scala.reflect.runtime.universe.TypeTag
中包含的类型(即scala.reflect.api.TypeTags#TypeTag
)。我忽略了镜子。镜子也应该进行比较吗? (换句话说,如果两个类型标签具有兼容类型但不同的镜像,它们是否可以分配兼容?)
问题7:(可能与问题6有关。)如果不同的类加载器加载了相同限定名称的类型,会发生什么?在这种情况下,上面的代码是否正确? (即,根据我的理解,不应该将从类加载器1加载的TypeTags
转换为从类加载器2加载的typeTag.tpe
。)
问题8(answered): test.Test
这里是正确的工具吗?或者我应该用test.Test
s直接标记对象? (毕竟,我只在TypeTag
中比较类型。)但据我所知(从各种讨论中),Type
被提出作为标记类型安全类的问题的解决方案。为什么?或者cast
是什么?
问题9:对此表现有何评论?在运行时比较两种类型(使用TypeTag
)听起来可能很昂贵......有替代方案吗? (我想可能构建一个从TypeTag
- 到<:<
的地图来记住哪些类型是赋值兼容的。但是,这样的查找会更快吗?TypeTags
s不据我所知,它具有用于快速查找的唯一ID。(GHC使用&#34;指纹&#34;对此,我认为。)
问题10:还有其他观察结果吗?我做错了什么?我的结论是Boolean
是否类型安全正确?
答案 0 :(得分:0)
编辑:此解决方案有误。使用此解决方案时,仍然可以使用Typesafe
,然后将该类强制转换为任何类型。
对问题3的回答(我们是否可以优化特征Typesafe
的定义,以便无法以某种方式实例化T
以使投射行为成为可能不正确?)
这可以通过指定“自我类型”来完成(参见spec)。在特征中,可以指定继承该类的类应具有的类型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
的类都必须是class X extends Typesafe[T]
类型。也就是说,如果T
是X
的超类型,则只能编写Typesafe
,因此提供给X
的类型标记将保证为foo(x)
的超类型{1}}。
答案 1 :(得分:0)
回答问题8 (TypeTag在这里是正确的乐器吗?或者我应该直接用类型标记对象?):
一个原因是TypeTag
有一个类型参数,表示标记的类型。也就是说,TypeTag[T]
静态强制我们TypeTag
为T
。如果我们改为使用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
除了类型之外还需要有镜像。)