33

前段时间,Oracle 决定在 Java 8 中添加闭包是一个好主意。我想知道与 Scala 相比,那里的设计问题是如何解决的,Scala 从第一天开始就关闭了。

引用javac.info的未解决问题

  1. 方法句柄可以用于函数类型吗? 如何使其工作尚不清楚。一个问题是方法句柄具体化了类型参数,但是以一种干扰函数子类型的方式。

  2. 我们可以摆脱“抛出”类型参数的显式声明吗? 这个想法是在声明的绑定是检查异常类型时使用析取类型推断。这不是严格向后兼容的,但不太可能破坏真实的现有代码。然而,由于句法歧义,我们可能无法摆脱类型参数中的“抛出”。

  3. 禁止在旧式循环索引变量上使用 @Shared

  4. 处理像 Comparator 这样定义多个方法的接口,除了其中一个方法之外,所有这些接口都将由继承自 Object 的方法实现。“单一方法的接口”的定义应该只计算不会由 Object 中的方法实现的方法,并且如果实现其中一个方法将实现所有方法,则应该将多个方法计算为一个。主要是,这需要更精确地说明接口只有一个抽象方法意味着什么。

  5. 指定函数类型到接口的映射:名称、参数等。我们应该完全指定函数类型到系统生成的接口的映射。

  6. 类型推断。需要扩充类型推断的规则以适应异常类型参数的推断。同样,闭包转换使用的子类型关系也应该体现出来。

  7. 删除异常类型参数以帮助改进异常透明度。 也许使省略的异常类型参数意味着界限。这可以通过添加新的泛型异常参数来改造没有异常类型参数的现有泛型接口,例如 java.util.concurrent.Callable。

  8. 函数类型的类文字是如何形成的? 是 #void().class 吗?如果是这样,如果对象类型被擦除,它是如何工作的?是 #?(?).class 吗?

  9. 系统类加载器应该动态生成函数类型接口。 与函数类型对应的接口应该由引导类加载器按需生成,以便在所有用户代码之间共享。对于原型,我们可能让 javac 生成这些接口,以便原型生成的代码可以在库存 (JDK5-6) 虚拟机上运行。

  10. lambda 表达式的求值必须每次都产生一个新对象吗? 希望不会。例如,如果 lambda 没有从封闭范围捕获变量,则可以静态分配它。同样,在其他情况下,如果 lambda 未捕获循环内声明的任何变量,则可以将其移出内部循环。因此,如果规范对 lambda 表达式结果的引用身份不承诺任何内容,那将是最好的,这样编译器就可以完成这种优化。

据我了解 2.、6. 和 7. 在 Scala 中不是问题,因为 Scala 不使用 Checked Exceptions 作为某种“影子类型系统”,如 Java。

其余的呢?

4

2 回答 2

29

1) 方法句柄可以用于函数类型吗?

Scala 针对没有方法句柄的 JDK 5 和 6,因此它还没有尝试处理这个问题。

2)我们可以摆脱“抛出”类型参数的显式声明吗?

Scala 没有检查异常。

3) 在旧式循环索引变量上禁止 @Shared。

Scala 没有循环索引变量。不过,同样的想法可以用某种 while 循环来表达。Scala 的语义在这里非常标准。符号绑定被捕获,如果符号碰巧映射到一个可变的参考单元格,那么你自己就可以了。

4) 处理像 Comparator 这样定义多个方法的接口,除了其中一个来自 Object

Scala 用户倾向于使用函数(或隐式函数)将正确类型的函数强制转换为接口。例如

[implicit] def toComparator[A](f : (A, A) => Int) = new Comparator[A] { 
    def compare(x : A, y : A) = f(x, y) 
}

5) 指定从函数类型到接口的映射:

Scala 的标准库包括 0 <= N <= 22 的 FuncitonN 特征,并且规范说函数文字创建这些特征的实例

6)类型推断。需要扩充类型推断的规则以适应异常类型参数的推断。

由于 Scala 没有检查异常,因此它可以解决整个问题

7) 删除​​异常类型参数以帮助改进异常透明度。

同样的交易,没有检查异常。

8) 函数类型的类文字是如何形成的?是 #void().class 吗?如果是这样,如果对象类型被擦除,它是如何工作的?是 #?(?).class 吗?

classOf[A => B] //or, equivalently, 
classOf[Function1[A,B]]

类型擦除是类型擦除。无论 A 和 B 的选择如何,上述文字都会产生 scala.lang.Function1。如果您愿意,可以编写

classOf[ _ => _ ] // or
classOf[Function1[ _,_ ]]

9) 系统类加载器应动态生成函数类型接口。

Scala 任意将参数的数量限制为最多 22 个,这样它就不必动态生成 FunctionN 类。

10) lambda 表达式的求值必须每次都产生一个新对象吗?

Scala 规范并没有说它必须。但是从 2.8.1 开始,编译器不会优化 lambda 无法从其环境中捕获任何内容的情况。我还没有用 2.9.0 测试过。

于 2011-05-26T18:20:56.080 回答
12

我将在这里只讨论第 4 点。

Java“闭包”与其他语言中的闭包的区别之一是它们可以用来代替不描述函数的接口——例如,Runnable. 这就是 SAM 的含义,即单一抽象方法。

Java 这样做是因为这些接口在 Java 库中比比皆是,它们在 Java 库中比比皆是,因为Java 是在没有函数类型或闭包的情况下创建的。在他们缺席的情况下,每个需要控制反转的代码都必须求助于使用 SAM 接口。

例如,Arrays.sort获取一个Comparator对象,该对象将在要排序的数组成员之间进行比较。相比之下,Scala 可以List[A]通过接收一个函数来对 a 进行排序(A, A) => Int,该函数很容易通过闭包传递。但是,请参见最后的注释 1。

因此,由于 Scala 的库是为具有函数类型和闭包的语言创建的,因此不需要在 Scala 中支持诸如 SAM 闭包之类的东西。

当然,还有一个 Scala/Java 互操作性的问题——虽然 Scala 的库可能不需要 SAM 之类的东西,但Java库却需要。有两种方法可以解决。首先,因为 Scala 支持闭包和函数类型,所以很容易创建辅助方法。例如:

def runnable(f: () => Unit) = new Runnable {
    def run() = f()
}

runnable { () => println("Hello") } // creates a Runnable

实际上,通过使用 Scala 的别名参数可以使这个特定的示例更短,但这不是重点。无论如何,可以说,这是 Java 可以做的事情,而不是它打算做的事情。鉴于 SAM 接口的普及,这并不令人惊讶。

Scala 处理这个问题的另一种方式是通过隐式转换。通过仅添加implicit到上述runnable方法的前面,可以创建一种方法,该方法可以在Runnable需要 a 但提供函数时自动应用(注 2) () => Unit

然而,隐式非常独特,并且在某种程度上仍然存在争议。

注1:实际上,这个特定的例子是出于某种恶意选择的……Comparator两个抽象方法而不是一个,这是它的全部问题。由于其中一种方法可以根据另一种方法实现,我认为他们只会从抽象列表中“减去”防御者方法。

而且,在 Scala 方面,即使有一个排序方法使用(A, A) => Boolean,而不是(A, A) => Int,标准排序方法调用一个Ordering对象,这与 Java 的Comparator! 但是,在 Scala 中,Ordering它扮演了类型类的角色。

注意 2一旦将隐式导入到 scope中,就会自动应用它们。

于 2011-05-26T22:49:51.170 回答