Java 7 当前 Lambda 提案的复杂性?(2010年8月)

有人说,每一种编程语言都有其“复杂性预算”,可以用来实现其目的。但是,如果复杂性预算耗尽,每个微小的更改都会变得越来越复杂,并且难以以向后兼容的方式实施。

在阅读了 2010 年 8 月 Lambda 的当前临时语法(≙ Lambda 表达式、异常透明度、防御方法和方法参考)之后,我想知道 Oracle 的人在考虑此类更改时是否完全忽略了 Java 的复杂性预算。

这些是我正在考虑的问题 - 其中一些更多的是关于语言设计:

  • 拟议的增加内容在复杂性上是否与其他语言选择的方法相当?
  • 通常是否可以将此类添加添加到语言中保护开发人员免受实现复杂性的影响?
  • 这些添加是Java作为语言发展结束的标志,还是在改变具有悠久历史的语言时可以预料到的?
  • 在语言进化的这一点上,其他语言是否采取了完全不同的方法?

谢谢!


答案 1

我没有遵循Java 7 lambda提案的过程和发展,我甚至不确定最新的提案措辞是什么。将此视为咆哮/意见,而不是真理的陈述。另外,我已经很久没有使用Java了,所以语法可能有些生锈,而且不正确。

首先,Java语言的lambdas是什么?句法糖。虽然一般来说,lambda使代码能够就地创建小的函数对象,但这种支持已经通过使用内部类在Java语言中预设了 - 在某种程度上。

那么 lambdas 的语法有多好呢?它的性能在哪些方面优于以前的语言结构?哪里可以更好?

对于初学者来说,我不喜欢lambda函数有两种可用语法的事实(但这符合C#的行,所以我想我的观点并不普遍。我想如果我们想糖衣,那么比即使双语法不添加任何其他内容也更甜。我更喜欢第二种语法,更通用,但需要额外的写作成本和简短的版本。#(int x)(x*x)#(int x){ return x*x; }return;

为了真正有用,lambdas可以从定义变量的范围和闭包中获取变量。为了与内部类保持一致,lambdas仅限于捕获“有效的最终”变量。与该语言先前功能的一致性是一个不错的功能,但为了甜蜜起见,能够捕获可以重新分配的变量会很好。为此,他们正在考虑通过引用捕获上下文中存在并带有注释的变量,从而允许赋值。对我来说,这似乎很奇怪,因为lambda如何使用变量是在变量的声明位置而不是在定义lambda的位置确定的。单个变量可以在多个 lambda 中使用,这会强制它们在所有 lambda 中具有相同的行为。@Shared

Lambdas尝试模拟实际的函数对象,但建议并没有完全实现:保持解析器的简单性,因为到目前为止,标识符表示一个对象或一个保持一致的方法,调用lambda需要使用lambda名称后面的a:将返回。这带来了一种新的语法,用于与语言其他部分不同的lambda,其中以某种方式作为虚拟通用接口上的同义词,但是,为什么不让它完整呢?!#(int x)(x*x)!(5)25!.executeLambda<Result,Args...>

可以创建新的通用(虚拟)接口。它必须是虚拟的,因为接口不是一个真正的接口,而是一个这样的系列:,,...他们可以定义一个单一的执行方法,我希望像C++一样,但如果这是一个负担,那么任何其他名称都可以,将 the 作为方法执行的快捷方式:LambdaLambda<Return>Lambda<Return,Arg1>Lambda<Return,Arg1,Arg2>operator()!

 interface Lambda<R> {
    R exec();
 }
 interface Lambda<R,A> {
    R exec( A a );
 }

然后编译器只需要翻译成 ,这很简单。lambda 语法的转换将要求编译器识别正在实现的正确接口,并且可以匹配为:identifier!(args)identifier.exec( args )

 #( int x )(x *x)
 // translated to
 new Lambda<int,int>{ int exec( int x ) { return x*x; } }

这也将允许用户定义在更复杂的情况下可以用作lambda的内部类。例如,如果 lambda 函数需要捕获以只读方式注释的变量,或者在捕获位置维护捕获对象的状态,则可以手动实现 Lambda:@Shared

 new Lambda<int,int>{ int value = context_value;
     int exec( int x ) { return x * context_value; }
 };

其方式类似于当前的内部类定义,因此对于当前的Java用户来说是自然的。例如,这可以在循环中用于生成乘数 lambda:

 Lambda<int,int> array[10] = new Lambda<int,int>[10]();
 for (int i = 0; i < 10; ++i ) {
    array[i] = new Lambda<int,int>{ final int multiplier = i;
       int exec( int x ) { return x * multiplier; }
    };
 }
 // note this is disallowed in the current proposal, as `i` is
 // not effectively final and as such cannot be 'captured'. Also
 // if `i` was marked @Shared, then all the lambdas would share
 // the same `i` as the loop and thus would produce the same
 // result: multiply by 10 --probably quite unexpectedly.
 //
 // I am aware that this can be rewritten as:
 // for (int ii = 0; ii < 10; ++ii ) { final int i = ii; ...
 //
 // but that is not simplifying the system, just pushing the
 // complexity outside of the lambda.

这将允许使用lambdas和接受lambda的方法,这两者都使用新的简单语法:或者对于糖衣干扰预期语义的特定情况,使用更复杂的手动方法。#(int x){ return x*x; }

总的来说,我相信lambda提案可以在不同的方向上进行改进,它添加语法糖的方式是一个泄漏的抽象(你已经在外部处理了lambda特有的问题),并且通过不提供较低级别的接口,它使用户代码在不完全适合简单用例的用例中可读性降低。:


答案 2

模一些范围消除歧义的构造,几乎所有这些方法都遵循lambda抽象的实际定义:

λx.E

要按顺序回答您的问题:

我不认为有任何特定的事情使Java社区的提案比其他任何东西都更好或更差。正如我所说,它遵循数学定义,因此所有忠实的实现都将具有几乎完全相同的形式。

固定在命令式语言上的匿名一流函数往往最终会成为一些程序员喜欢和经常使用的功能,而其他人则完全忽略 - 因此,给它一些语法可能是一个明智的选择,这些语法不会混淆那些选择忽略这种特定语言功能的人。我认为隐藏实现的复杂性和细节是他们试图通过使用与Java很好地融合的语法来做的事情,但这对Java程序员来说没有真正的内涵。

他们可能希望使用一些不会使现有定义复杂化的语法,因此他们可以选择用作运算符等的符号受到轻微限制。当然,Java坚持保持向后兼容,这稍微限制了语言的发展,但我不认为这一定是一件坏事。PHP方法处于频谱的另一端(即“每次有新的点发布时,让我们打破一切!”)。我不认为Java的发展受到固有的限制,除了其设计的一些基本原则 - 例如,遵守基于VM的OOP原则。

我认为从Java的角度对语言演变做出强有力的陈述是非常困难的。它处于一个相当独特的位置。首先,它非常非常受欢迎,但它相对较旧。微软在决定开始设计一种名为“C#”的语言之前,已经拥有了至少10年的Java遗产。C编程语言基本上停止了发展。C++几乎没有发生过任何主流接受的重大变化。Java通过一个缓慢但一致的过程继续发展 - 如果有的话,我认为它比任何其他具有类似巨大安装代码库的语言更有能力继续发展。