2

我发现自己看了一些 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有难闻的气味。对这种方法有什么建议吗?让我补充两件事

  • 将类型参数从 trait 的构造函数移动Serializerread方法不是一种选择(有特定的序列化程序需要参数化的值参数S
  • 向 的类型构造函数参数添加方差Serializer不是一种选择
4

1 回答 1

2

介绍:

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

暂定答案:

在编译时,对于任何实例,Ser[T]都会有一个定义明确的类型参数,因为您想在实例化时保存它,所以对于给定的特定类型T,您将有一个 anySerSer[T]A

你在某种程度上说的是 a Ser[A]will 像Ser[S]any一样工作S。根据A型和S型的关系,这可以用两种方式来解释。

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

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

  3. 如果它不是前一种情况,那么这意味着:

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

    在运行时可能会产生可怕的失败,因为会有一些 S 序列化程序无法工作的 S。如果没有可能发生这种情况的 S,那么您的班级属于 1 或

评论后编辑

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

总的来说,我认为应该非常小心地使用这种做法。强类型语言的好处之一是编译器执行正式的类型检查,帮助您发现早期错误。如果你故意破坏它,你就放弃了这个巨大的好处。

于 2013-01-18T12:30:01.347 回答