2016-07-17 33 views
0

我想实现可以选择进行类型安全演员(例如,x.cast[List[U]])的参数化类(如List[T])。使用类型标签的Typesafe仿制药(安全演员)

通过类型安全我的意思是如果类型在运行时不正确,则可能会抛出异常,但可以保证如果转换成功,则结果值的类型为List[U]。 (例如,asInstanceOf没有做到这一点。List(1,2,3).asInstanceOf[List[String]]会成功,但返回List不包含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}") 
    } 
} 

的逻辑是:继承Typesafe[T]T将有实例typeTagTypeTag[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[...]条款。

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]]。我不知道这是可能的。

问题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]] { 
} 

在这里,我们有“不小心”在Typesafe性状的参数使用TypesafeList。这编译好,但这意味着现在TypesafeOption将有typeTagTypesafeList!(因此,cast中的检查将不正确,并且可能会发生错误转换。)我相信这样的混音可以很容易地发生,如果编译器能够捕捉到这种混音,那就太好了。 (在一定程度上,该类型约束T <: Typesafe[T]已经避免这种mixups(以下this),但遗憾的是没有一个在TypesafeOption。)

问题3(answered):我们可以细化特征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 s?

最后,我有一个关于整体例如一些问题:

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

问题6:我正在比较TypeTags(即typeTag.tpe)中包含的类型。我忽略了镜子。是否应该对镜子进行比较? (换句话说,如果两个类型的变量具有兼容的类型,但不同的镜子,他们将分配兼容?)

问题7:(可能涉及到问题6)会发生什么,如果类型相同的限定名称是否已被不同的类加载器加载?上面的代码在这种情况下是否正确? (即,它不应该是可能的投test.Test从类加载器加载1,从类加载器加载2 test.Test,据我了解。)

问题8(answered):TypeTag这里的合适的仪器?或者我应该直接用Type s来标记对象? (毕竟,我仅在cast中比较了这些类型)。但据我所知(从各种讨论中),TypeTag s是作为类型安全类标记问题的解决方案提供的。为什么?或者TypeTag是什么?

问题9:对此表现有何评论?在运行时比较两种类型(使用<:<)听起来可能很昂贵...是否有其他选择? (我想可能是从TypeTags -pairs到Boolean这样的地图来记录哪些类型是分配兼容的,但是这样的查找速度会更快吗?TypeTags没有用于快速查找的唯一ID,据我所知。 (GHC为此使用“指纹”,我想。))

问题10:还有其他意见吗?我做错了什么?我的结论是cast是类型安全正确的吗?

+0

LT;博士。你的第一部分看起来像是https://github.com/milessabin/shapeless。 – pedrofurla

+0

更具体地说:https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/typeable.scala –

+0

shapeless/Typeable有一个类似的目标,但是有两个重要的区别: (a)通过递归遍历数据结构(例如,在list.cast(List [Int])中检查cast是否安全),它将遍历列表中的所有元素并检查它们是否是整数。) (b)需要为任何可能用作类型参数的类型定义一个Typeable实例。 (即,对于'list.cast(List [Bla])',我们需要一个Typeable [Bla]在范围内。)这使得它更难作为一个库来使用(因为用户必须提供Typeable-instances为他所有的课程。 –

回答

0

编辑:这种解决方案是错误的。使用此解决方案时,仍然可以使用val typeTag = typeTag[Nothing],然后将该类转换为任何类型。

回答问题3我们可以细化特征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

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需要有一个镜在它除了类型。)