TypeTfe泛型使用TypeTag(带安全转换)

时间:2016-07-17 22:21:51

标签: scala

我想实现可以选择进行类型安全转换的参数类(如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 TypesafeOptiontypeTag! (因此,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类,即TypeTagTypeTag。哪一个在这里是正确的?

问题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是否类型安全正确?

2 个答案:

答案 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]类型。也就是说,如果TX的超类型,则只能编写Typesafe,因此提供给X的类型标记将保证为foo(x) 的超类型{1}}。

答案 1 :(得分:0)

回答问题8 (TypeTag在这里是正确的乐器吗?或者我应该直接用类型标记对象?)

一个原因是TypeTag有一个类型参数,表示标记的类型。也就是说,TypeTag[T]静态强制我们TypeTagT。如果我们改为使用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除了类型之外还需要有镜像。)