Scala 中的闭包与 Java 中的闭包
不久前,Oracle 认为向 Java 8 添加闭包将是一个好主意。我想知道与 Scala 相比,那里的设计问题是如何解决的,Scala 从第一天起就已经关闭了。
引用 javac.info 中的未决问题 :
方法句柄可以用于函数类型吗? 如何使其发挥作用尚不清楚。一个问题是方法句柄具体化了类型参数,但是以一种干扰函数子类型的方式。
我们可以摆脱“抛出”类型参数的显式声明吗? 这个想法是只要声明的边界是受检查的异常类型,就使用析取类型推断。这并不严格向后兼容,但不太可能破坏实际的现有代码。然而,由于语法歧义,我们可能无法摆脱类型参数中的“抛出”。
在旧式循环索引变量上不允许使用 @Shared
处理类似接口定义多个方法的比较器,除了其中一个之外,所有方法都将由从 Object 继承的方法实现。 “具有单个方法的接口”的定义应该只计算那些不会被 Object 中的方法实现的方法,并且如果实现其中一个方法将实现所有方法,则应该将多个方法算作一个方法。主要是,这需要更精确地规范接口仅具有单个抽象方法的含义。
指定从函数类型到接口的映射:名称、参数等。 我们应该完全精确地指定从函数类型到系统生成的接口的映射。
类型推断。需要扩充类型推断规则以适应异常类型参数的推断。同样,闭包转换使用的子类型关系也应该得到反映。
隐藏异常类型参数以帮助改进异常透明度。 也许使省略的异常类型参数意味着界限。这样可以通过添加新的泛型异常参数来改造没有异常类型参数的现有泛型接口,例如 java.util.concurrent.Callable。
函数类型的类文字是如何形成的? 是#void().class吗?如果是这样,如果对象类型被删除,它会如何工作?是 #?(?).class 吗?
系统类加载器应该动态生成函数类型接口。 与函数类型对应的接口应该由引导类加载器按需生成,因此它们可以在所有用户代码之间共享。对于原型,我们可以让 javac 生成这些接口,以便原型生成的代码可以在库存 (JDK5-6) VM 上运行。
lambda 表达式的求值每次都必须生成一个新的对象吗? 但愿不会。例如,如果 lambda 没有从封闭范围捕获任何变量,则可以静态分配它。同样,在其他情况下,如果 lambda 不捕获循环内声明的任何变量,则可以将其移出内部循环。因此,最好规范不承诺 lambda 表达式结果的引用标识,这样编译器就可以完成此类优化。
据我了解,2.、6. 和 7. 在 Scala 中不是问题,因为 Scala 不使用检查异常作为某种“影子类型系统”(如 Java)。
剩下的呢?
Some time ago Oracle decided that adding Closures to Java 8 would be an good idea. I wonder how design problems are solved there in comparison to Scala, which had closures since day one.
Citing the Open Issues from javac.info:
Can Method Handles be used for Function Types?
It isn't obvious how to make that work. One problem is that Method Handles reify type parameters, but in a way that interferes with function subtyping.Can we get rid of the explicit declaration of "throws" type parameters?
The idea would be to use disjuntive type inference whenever the declared bound is a checked exception type. This is not strictly backward compatible, but it's unlikely to break real existing code. We probably can't get rid of "throws" in the type argument, however, due to syntactic ambiguity.Disallow @Shared on old-style loop index variables
Handle interfaces like Comparator that define more than one method, all but one of which will be implemented by a method inherited from Object.
The definition of "interface with a single method" should count only methods that would not be implemented by a method in Object and should count multiple methods as one if implementing one of them would implement them all. Mainly, this requires a more precise specification of what it means for an interface to have only a single abstract method.Specify mapping from function types to interfaces: names, parameters, etc.
We should fully specify the mapping from function types to system-generated interfaces precisely.Type inference. The rules for type inference need to be augmented to accomodate the inference of exception type parameters. Similarly, the subtype relationships used by the closure conversion should be reflected as well.
Elided exception type parameters to help retrofit exception transparency.
Perhaps make elided exception type parameters mean the bound. This enables retrofitting existing generic interfaces that don't have a type parameter for the exception, such as java.util.concurrent.Callable, by adding a new generic exception parameter.How are class literals for function types formed?
Is it #void().class ? If so, how does it work if object types are erased? Is it #?(?).class ?The system class loader should dynamically generate function type interfaces.
The interfaces corresponding to function types should be generated on demand by the bootstrap class loader, so they can be shared among all user code. For the prototype, we may have javac generate these interfaces so prototype-generated code can run on stock (JDK5-6) VMs.Must the evaluation of a lambda expression produce a fresh object each time?
Hopefully not. If a lambda captures no variables from an enclosing scope, for example, it can be allocated statically. Similarly, in other situations a lambda could be moved out of an inner loop if it doesn't capture any variables declared inside the loop. It would therefore be best if the specification promises nothing about the reference identity of the result of a lambda expression, so such optimizations can be done by the compiler.
As far as I understand 2., 6. and 7. aren't a problem in Scala, because Scala doesn't use Checked Exceptions as some sort of "Shadow type-system" like Java.
What about the rest?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
1) 方法句柄可以用于函数类型吗?
Scala 的目标是 JDK 5 和 6,它们没有方法句柄,因此它尚未尝试处理该问题。
2)我们可以摆脱“抛出”类型参数的显式声明吗?
Scala 没有检查异常。
3) 不允许在旧式循环索引变量上使用 @Shared。
Scala 没有循环索引变量。尽管如此,同样的想法可以用某种 while 循环来表达。 Scala 的语义在这里非常标准。符号绑定被捕获,如果符号碰巧映射到可变引用单元格,那么就由你自己决定了。
4) 处理像 Comparator 这样的接口,它定义了多个方法,除了其中一个方法之外,所有这些方法都来自于 Object
Scala 用户倾向于使用函数(或隐式函数)将正确类型的函数强制到接口。例如
5) 指定从函数类型到接口的映射:
Scala 的标准库包含 0 <= N <= 22 的 FuncitonN 特征,并且规范规定函数文字创建这些特征的实例
6 ) 类型推断。需要增强类型推断规则以适应异常类型参数的推断。
由于 Scala 没有检查异常,因此可以在整个问题上进行处理
7) 消除异常类型参数以帮助改进异常透明度。
同样的交易,没有受检查的异常。
8) 函数类型的类文字是如何形成的?是#void().class吗?如果是这样,如果对象类型被删除,它会如何工作?是 #?(?).class 吗?
类型擦除就是类型擦除。无论 A 和 B 选择如何,上面的文字都会生成 scala.lang.Function1。如果您愿意,可以这样写
9) 系统类加载器应该动态生成函数类型接口。
Scala 任意限制数量参数最多为 22 个,这样就不必动态生成 FunctionN 类。
10) lambda 表达式的计算每次都必须生成一个新对象吗?
Scala 规范并未规定必须如此。但从 2.8.1 开始,编译器不会优化 lambda 不从其环境中捕获任何内容的情况。我还没有测试过2.9.0。
1) Can Method Handles be used for Function Types?
Scala targets JDK 5 and 6 which don't have method handles, so it hasn't tried to deal with that issue yet.
2) Can we get rid of the explicit declaration of "throws" type parameters?
Scala doesn't have checked exceptions.
3) Disallow @Shared on old-style loop index variables.
Scala doesn't have loop index variables. Still, the same idea can be expressed with a certain kind of while loop . Scala's semantics are pretty standard here. Symbols bindings are captured and if the symbol happens to map to a mutable reference cell then on your own head be it.
4) Handle interfaces like Comparator that define more than one method all but one of which come from Object
Scala users tend to use functions (or implicit functions) to coerce functions of the right type to an interface. e.g.
5) Specify mapping from function types to interfaces:
Scala's standard library includes FuncitonN traits for 0 <= N <= 22 and the spec says that function literals create instances of those traits
6) Type inference. The rules for type inference need to be augmented to accomodate the inference of exception type parameters.
Since Scala doesn't have checked exceptions it can punt on this whole issue
7) Elided exception type parameters to help retrofit exception transparency.
Same deal, no checked exceptions.
8) How are class literals for function types formed? Is it #void().class ? If so, how does it work if object types are erased? Is it #?(?).class ?
Type erasure is type erasure. The above literals produce scala.lang.Function1 regardless of the choice for A and B. If you prefer, you can write
9) The system class loader should dynamically generate function type interfaces.
Scala arbitrarily limits the number of arguments to be at most 22 so that it doesn't have to generate the FunctionN classes dynamically.
10) Must the evaluation of a lambda expression produce a fresh object each time?
The Scala specification does not say that it must. But as of 2.8.1 the the compiler does not optimizes the case where a lambda does not capture anything from its environment. I haven't tested with 2.9.0 yet.
我在这里只讨论第四点。
Java“闭包”与其他语言中的闭包的区别之一是它们可以用来代替不描述函数的接口——例如,
Runnable
。这就是 SAM(单一抽象方法)的含义。Java 这样做是因为这些接口在 Java 库中比比皆是,而它们在 Java 库中比比皆是,因为 Java 是在没有函数类型或闭包的情况下创建的。如果没有它们,每个需要控制反转的代码都必须求助于 SAM 接口。
例如,
Arrays.sort
采用一个Comparator
对象,该对象将在要排序的数组成员之间执行比较。相比之下,Scala 可以通过接收函数(A, A) => 对
,很容易通过闭包传递。不过,请参阅最后的注释 1。List[A]
进行排序。 Int因此,由于 Scala 的库是为具有函数类型和闭包的语言创建的,因此不需要在 Scala 中支持 SAM 闭包之类的东西。
当然,存在一个 Scala/Java 互操作性问题——虽然 Scala 的库可能不需要 SAM 之类的东西,但 Java 库却需要。有两种方法可以解决。首先,因为 Scala 支持闭包和函数类型,所以创建辅助方法非常容易。例如:
实际上,通过使用 Scala 的按名称参数可以使这个特定示例变得更短,但这不是重点。不管怎样,可以说,这是 Java 本来可以做的事情,而不是它将会做的事情。鉴于 SAM 接口的流行,这并不奇怪。
Scala 处理此问题的另一种方法是通过隐式转换。只需将
implicit
添加到上面的runnable
方法中,就可以创建一个方法,只要需要Runnable
但函数() =>提供单位
。然而,隐式非常独特,并且在某种程度上仍然存在争议。
注1:实际上,这个特定的示例是出于某种恶意而选择的...
Comparator
有两个个抽象方法,而不是一个,这是整个有问题。由于其中一种方法可以根据另一种方法来实现,我认为他们只会从抽象列表中“减去”防御者方法。而且,在 Scala 方面,即使有一个使用
(A, A) => 的排序方法,布尔值
,而不是(A, A) => Int
,标准排序方法需要一个Ordering
对象,这与Java的Comparator
非常相似!但在 Scala 的例子中,Ordering
执行类型类的角色。注释 2:一旦将隐式导入范围,就会自动应用隐式。
I'll address only number 4 here.
One of the things that distinguishes Java "closures" from closures found in other languages is that they can be used in place of interface that does not describe a function -- for example,
Runnable
. This is what is meant by SAM, Single Abstract Method.Java does this because these interfaces abound in Java library, and they abound in Java library because Java was created without function types or closures. In their absence, every code that needed inversion of control had to resort to using a SAM interface.
For example,
Arrays.sort
takes aComparator
object that will perform comparison between members of the array to be sorted. By contrast, Scala can sort aList[A]
by receiving a function(A, A) => Int
, which is easily passed through a closure. See note 1 at the end, however.So, because Scala's library was created for a language with function types and closures, there isn't need to support such a thing as SAM closures in Scala.
Of course, there's a question of Scala/Java interoperability -- while Scala's library might not need something like SAM, Java library does. There are two ways that can be solved. First, because Scala supports closures and function types, it is very easy to create helper methods. For example:
Actually, this particular example can be made even shorter by use of Scala's by-name parameters, but that's beside the point. Anyway, this is something that, arguably, Java could have done instead of what it is going to do. Given the prevalence of SAM interfaces, it is not all that surprising.
The other way Scala handles this is through implicit conversions. By just prepending
implicit
to therunnable
method above, one creates a method that gets automatically (note 2) applied whenever aRunnable
is required but a function() => Unit
is provided.Implicits are very unique, however, and still controversial to some extent.
Note 1: Actually, this particular example was choose with some malice...
Comparator
has two abstract methods instead of one, which is the whole problem with it. Since one of its methods can be implemented in terms of the other, I think they'll just "subtract" defender methods from the abstract list.And, on the Scala side, even though there's a sort method that uses
(A, A) => Boolean
, not(A, A) => Int
, the standard sorting method calls for aOrdering
object, which is quite similar to Java'sComparator
! In Scala's case, though,Ordering
performs the role of a type class.Note 2: Implicits are automatically applied, once they have been imported into scope.