繁体   English   中英

使用TypeTag的Typesafe泛型(带有安全强制转换)

[英]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]]将成功,但返回不包含StringList 。)

我的方法是使用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:如何获取所需TypeTagtoSet (一个选择是将类型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将有typeTagTypesafeList (因而在检查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 TypesafeListTypeTag 我们需要在该行中显式添加(typeTag[TypesafeList[Int]]) (原因是new TypesafeList期望使用TypeTag[_ <: TypesafeList[Int]] ,并且编译器不够聪明,无法看到他可以构造TypeTag[TypesafeList[Int]] 。)

问题4:我们如何定义TypesafeList以便不需要显式地提供TypeTag

最后,我对整个示例有一些疑问:

问题5:运行时(至少)有两个不同的TypeTag类,分别是scala.reflect.runtime.universe.TypeTagscala.reflect.api.TypeTags#TypeTag 这里哪一个是正确的?

问题6:我正在比较TypeTags包含的类型(即typeTag.tpe )。 我无视镜子。 也应该比较镜子吗? (换句话说,如果两个类型标签具有兼容的类型但具有不同的镜像,那么它们是否可以兼容分配?)

问题7 :(可能与问题6有关。)如果相同的合格名称的类型已由不同的类加载器加载,会发生什么情况? 在这种情况下,上面的代码正确吗? (据我所知,应该不可能将test.Test从类加载器1加载到test.Test从类加载器2加载。)

问题8( 回答 ): TypeTag在这里是正确的工具吗? 还是应该直接用Type标记对象? (毕竟,我只比较cast的类型。)但是据我所见(从各种讨论中), TypeTag提出来是为类型安全标记类问题的解决方案。 为什么? 还是TypeTag用于什么?

问题9:对此有何评论? 在运行时比较两种类型(使用<:< )听起来很昂贵……是否有替代方法? (我想过可能从TypeTagsBoolean的映射构造一个映射,该映射记住哪些类型是赋值兼容的。但是,这样的查找会更快吗?据我所知, 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类型。 也就是说,如果TX的超类型,则只能编写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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM