【问题标题】:Why do compile-time generative techniques for structural typing prevent separate compilation?为什么结构类型的编译时生成技术会阻止单独编译?
【发布时间】:2010-08-17 02:43:57
【问题描述】:

我正在阅读(好吧,略读) Dubochet 和 Odersky 的 Compiling Structural Types on the JVM,但对以下声明感到困惑:

生成技术创建 Java 接口以代替 用于 JVM 上的结构类型。此类的复杂性 技术在于,所有要用作的类 程序中任何地方的结构类型都必须实现 正确的接口。 当这在编译时完成时,它 防止单独编译。

(强调)

考虑论文中的自动关闭示例:

type Closeable = Any { def close(): Unit }

def autoclose(t: Closeable)(run: Closeable => Unit): Unit = {
   try { run(t) }
   finally { t.close }
}

我们不能为Closeable 类型生成一个接口,如下所示:

public interface AnonymousInterface1 {
   public void close();
}

并将我们对autoclose 的定义转换为

// UPDATE: using a view bound here, so implicit conversion is applied on-demand
def autoclose[T <% AnonymousInterface1](t: T)(run: T => Unit): Unit = {
   try { run(t) }
   finally { t.close }
}

然后考虑autoclose 的呼叫站点:

val fis = new FileInputStream(new File("f.txt"))
autoclose(fis) { ... }

由于fis是一个FileInputStream,它没有实现AnonymousInterface1,我们需要生成一个包装器:

class FileInputStreamAnonymousInterface1Proxy(val self: FileInputStream) 
      extends AnonymousInterface1 {
   def close() = self.close();
}

object FileInputStreamAnonymousInterface1Proxy {
   implicit def fis2proxy(fis: FileInputStream): FileInputStreamAnonymousInterface1Proxy =
      new FileInputStreamAnonymousInterface1Proxy(fis)
}

我一定错过了一些东西,但我不清楚它是什么。为什么这种方法会阻止单独编译?

【问题讨论】:

  • 这种方法不会阻止单独编译。但是,由于 Randall 的回答中解释的原因,它也不能像正常呼叫一样工作。

标签: scala structural-typing


【解决方案1】:

我从Scala-Inernals 邮件列表中的a discussion 中回忆起,问题在于对象身份,当前编译方法保留的对象身份在包装值时丢失。

【讨论】:

  • +1 获取有用的信息/链接,但这并不能回答我最初的问题——单独编译是如何受到损害的?
  • 重新打开这个问题(见我上面的更新)。视图绑定不会处理对象身份问题吗?
【解决方案2】:

考虑一下。考虑A类

class A { def a1(i: Int): String = { ... }; def a2(s: String): Boolean = { ... }

程序中的某个地方,可能在单独编译的库中,使用了这种结构类型:

{ def a1(i: Int): String }

在其他地方,使用这个:

{ def a2(s: String): Boolean }

除了全局分析之外,如何用必要的接口修饰类 A 以允许在指定那些广泛的结构类型的地方使用它?

如果给定类可能符合的每个可能的结构类型都用于生成捕获该结构类型的接口,那么此类接口就会爆炸式增长。请记住,结构类型可能会提到多个必需的成员,因此对于具有 N 个公共元素(val 或 defs)的类,这些 N 的所有可能子集都是必需的,这就是 N 的幂集,其基数为 2^N。

【讨论】:

  • 这是有道理的——我没有考虑过直接装饰类 A 的方法,我可以看到基于代理的方法是如何丢失对象身份的。
  • 我不确定您提交的哪些内容最好地回答了这个问题,因为它们确实只有在综合考虑时才会回答。既然都是你的,那我就收下这个吧。
【解决方案3】:

我实际上使用了您在Scala ARM library 中描述的隐式方法(使用类型类)。请记住,这是对问题的手动编码解决方案。

这里最大的问题是隐式解析。编译器不会即时为您生成包装器,您必须提前这样做并确保它们是隐式范围之一。这意味着(对于 Scala-ARM)我们为我们可以提供的任何资源提供“通用”包装器,并在我们找不到合适的包装器时回退到基于反射的类型。这样做的好处是允许用户使用普通的隐式规则指定自己的包装器。

请参阅:The Resource Type-trait 以及所有预定义的包装器。

另外,我在博客中更详细地描述了隐式解析魔法:Monkey Patching, Duck Typing and Type Classes

无论如何,您可能不想在每次使用结构类型时手动编码类型类。如果你真的希望编译器自动创建一个接口并为你做魔法,它可能会变得一团糟。每次定义结构类型时,编译器都必须为它创建一个接口(也许在以太的某个地方?)。我们现在需要为这些东西添加命名空间。此外,每次调用编译器都必须生成某种包装器实现类(同样是命名空间问题)。最后,如果我们有两个具有相同结构类型的不同方法分别编译,那么我们需要的接口数量就会激增。

并不是说这个障碍是无法克服的,但是如果你想对特定类型进行“直接”访问的结构化类型,那么 type-trait 模式似乎是你今天最好的选择。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-07-11
    • 1970-01-01
    • 1970-01-01
    • 2021-01-16
    • 1970-01-01
    • 1970-01-01
    • 2019-07-16
    • 2013-05-12
    相关资源
    最近更新 更多