3

我正在阅读(好的,略读) 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) { ... }

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

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

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

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

4

3 回答 3

7

正如我从关于Scala-Inernals邮件列表的讨论中回忆的那样,问题是对象标识,当前编译方法保留的对象标识在包装值时丢失。

于 2010-08-17T03:37:07.230 回答
4

想想看。考虑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。

于 2010-08-17T16:17:08.613 回答
1

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

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

请参阅: 资源类型特征及其所有预定义包装器。

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

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

Not that the hurdle couldn't be overcome, but if you want to have structural typing with "direct" access for particular types the type-trait pattern seems to be your best bet today.

于 2010-08-29T00:39:20.530 回答