铸造“空心”更高的kinded类型值以避免实例化

时间:2013-01-18 11:01:31

标签: scala casting type-erasure higher-kinded-types

我发现自己看了一下Scalawags#2录音,然后出现了关于类型擦除的部分,Dick Wall指出反射最终会让你陷入困境。

所以我在考虑一些我经常做的事情(我也看到了Scala Collections的实现)。假设我有一个带有序列化器的系统,将系统作为类型参数:

trait Sys[S <: Sys[S]] { type Tx }
trait FooSys extends Sys[FooSys]

trait Serializer[S <: Sys[S], A] {
  def read(implicit tx: S#Tx): A
}

现在有许多类型A可以构造序列化器而没有值参数,所以系统类型参数基本上是“空心的”。由于在我的示例中大量调用了序列化程序,我正在保存实例化:

object Test {
  def serializer[S <: Sys[S]] : Serializer[S, Test[S]] =
    anySer.asInstanceOf[Ser[S]]

  private val anySer = new Ser[FooSys]

  private final class Ser[S <: Sys[S]] extends Serializer[S, Test[S]] {
    def read(implicit tx: S#Tx) = new Test[S] {} // (shortened for the example)
  }
}
trait Test[S <: Sys[S]]

知道这是正确的,但当然,asInstanceOf有难闻的气味。是否对此方法有任何建议?让我添加两件事

  • 将type参数从trait Serializer的构造函数移动到read方法不是一个选项(有些特定的序列化程序需要在S中参数化的值参数)
  • Serializer的类型构造函数参数添加方差不是一个选项

1 个答案:

答案 0 :(得分:2)

<强>简介

我对你的例子感到有些困惑,我可能误解了你的问题,我觉得STx之间存在某种类型的递归,我没有从你的问题中得到(因为如果没有,S#Tx可能是任何东西,我不明白anySer)的问题

暂定答案:

在编译时,对于Ser[T]的任何实例,都会有一个定义良好的类型参数T,因为你想在实例化时保存它,你将拥有一个anySer {{1} }对于给定的特定类型Ser[T]

您在某种程度上说的是,ASer[A] Ser[S]起作用S。根据类型A和S之间的关系,可以用两种方式解释这一点。

  1. 如果每个A<:<S都可以进行此转换,那么您的序列化程序为COVARIANT,您可以将anySer初始化为Ser[Nothing]。由于Nothing是Scala中每个类的子类,因此anySer将始终作为Ser[Whatever]

  2. 工作
  3. 如果每个S<:<A都可以进行此转换,那么您的序列化程序为CONTRAVARIANT,您可以将anySer初始化为Ser[Any]。由于Any是Scala中每个类的子类,因此anySer将始终作为Ser[Whatever]

  4. 工作
  5. 如果它既不是前一个案例,那么它意味着:

    def serializer[S <: Sys[S]] : Serializer[S, Test[S]] =
    anySer.asInstanceOf[Ser[S]]
    

    可能会在运行时产生可怕的故障,因为会有一些S系列化程序无法正常工作。如果没有这样的S可能会发生这种情况,那么你的课程就属于1或

  6. 评论后编辑

    如果你的类型确实是不变的,那么通过强制转换的转换会破坏不变性关系。您基本上是强制类型系统执行非自然转换,因为根据您自己对所编写代码的了解,您知道不会发生任何错误。如果是这种情况,则转换是正确的方法:您正在强制使用与编译器可以正式检查的类型不同的类型,并且您正在明确这样做。我甚至会说一个很大的评论,说明为什么你知道这个操作是合法的,编译器无法猜测并最终附加一个漂亮的单元测试来验证“非正式”关系总是成立。

    总的来说,我认为应该谨慎使用这种做法。强类型语言的一个好处是编译器执行正式的类型检查,可以帮助您捕获早期错误。如果你有意破坏它,你会放弃这个巨大的好处。