我正在阅读(好的,略读)Dubochet和Odersky的Compiling Structural Types on the JVM,并对以下声明感到困惑:
生成技术创建Java接口 对于JVM上的结构类型。这种复杂性 技术在于所有要用作的类 程序中任何地方的结构类型都必须实现 正确的界面。 当它在编译时完成时,它 防止单独编译。
(强调补充)
考虑论文中的autoclose示例:
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)
}
我必须错过某些东西,但我不清楚它是什么。为什么这种方法会阻止单独编译?
答案 0 :(得分:7)
正如我在a discussion邮件列表上的Scala-Inernals回忆的那样,问题是当前编译方法保留的对象标识在包装值时会丢失。
答案 1 :(得分: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的powerset,其基数为2 ^ N.
答案 2 :(得分:1)
我实际上使用了您在Scala ARM library中描述的隐式方法(使用类型类)。请记住,这是一个解决问题的手工编码解决方案。
这里最大的问题是隐式解决方案。编译器不会为您动态生成包装器,您必须提前生成包装器并确保它们是隐式范围。这意味着(对于Scala-ARM)我们为任何资源提供“通用”包装器,并在找不到合适的包装器时回退到基于反射的类型。这样做的好处是允许用户使用普通的隐式规则指定自己的包装器。
请参阅:The Resource Type-trait以及所有预定义的包装器。
此外,我在博客中详细介绍了这种隐含分辨率魔法的技术:Monkey Patching, Duck Typing and Type Classes。
在任何情况下,您可能不希望每次使用结构类型时手动编码类型类。如果你真的希望编译器自动创建一个界面并为你做一些魔术,它可能会变得混乱。每次定义结构类型时,编译器都必须为它创建一个接口(也许在以太的某个地方?)。我们现在需要为这些东西添加名称空间。此外,每次调用时,编译器都必须生成某种类型的包装器实现类(同样具有命名空间问题)。最后,如果我们有两个不同的方法,它们具有相同的结构类型,我们只是分解了我们需要的接口数量。
并不是说障碍无法克服,但如果你想对特定类型进行“直接”访问的结构类型,那么类型特征模式似乎是你今天最好的选择。