Ale*_*lex 5 scala currying partial-application
在引擎盖方面:堆栈/堆分配,垃圾收集,资源和性能,以下三个有什么区别:
def Do1(a:String) = { (b:String) => { println(a,b) }}
def Do2(a:String)(b:String) = { println(a,b) }
def Do3(a:String, b:String) = { println(a,b) }
Do1("a")("b")
Do2("a")("b")
(Do3("a", _:String))("b")
Run Code Online (Sandbox Code Playgroud)
除了声明中每个采取和返回多少参数的明显表面差异
反编译以下类(注意Do2与您的问题相比的额外调用):
class Test {
def Do1(a: String) = { (b: String) => { println(a, b) } }
def Do2(a: String)(b: String) = { println(a, b) }
def Do3(a: String, b: String) = { println(a, b) }
Do1("a")("b")
Do2("a")("b")
(Do2("a") _)("b")
(Do3("a", _: String))("b")
}
Run Code Online (Sandbox Code Playgroud)
产生这个纯 Java 代码:
public class Test {
public Function1<String, BoxedUnit> Do1(final String a) {
new AbstractFunction1() {
public final void apply(String b) {
Predef..MODULE$.println(new Tuple2(a, b));
}
};
}
public void Do2(String a, String b) {
Predef..MODULE$.println(new Tuple2(a, b));
}
public void Do3(String a, String b) {
Predef..MODULE$.println(new Tuple2(a, b));
}
public Test() {
Do1("a").apply("b");
Do2("a", "b");
new AbstractFunction1() {
public final void apply(String b) {
Test.this.Do2("a", b);
}
}.apply("b");
new AbstractFunction1() {
public final void apply(String x$1) {
Test.this.Do3("a", x$1);
}
}.apply("b");
}
}
Run Code Online (Sandbox Code Playgroud)
(此代码无法编译,但足以进行分析)
让我们逐个查看它(每个清单中都有 Scala 和 Java):
def Do1(a: String) = { (b: String) => { println(a, b) } }
public Function1<String, BoxedUnit> Do1(final String a) {
new AbstractFunction1() {
public final void apply(String b) {
Predef.MODULE$.println(new Tuple2(a, b));
}
};
}
Run Code Online (Sandbox Code Playgroud)
无论如何Do1调用,都会创建一个新的 Function 对象。
def Do2(a: String)(b: String) = { println(a, b) }
public void Do2(String a, String b) {
Predef.MODULE$.println(new Tuple2(a, b));
}
def Do3(a: String, b: String) = { println(a, b) }
public void Do3(String a, String b) {
Predef.MODULE$.println(new Tuple2(a, b));
}
Run Code Online (Sandbox Code Playgroud)
Do2并Do3编译为相同的字节码。区别仅在于@ScalaSignature注释。
Do1("a")("b")
Do1("a").apply("b");
Run Code Online (Sandbox Code Playgroud)
Do1很简单:立即应用返回的函数。
Do2("a")("b")
Do2("a", "b");
Run Code Online (Sandbox Code Playgroud)
使用Do2,编译器会发现这不是部分应用程序,并将其编译为单个方法调用。
(Do2("a") _)("b")
new AbstractFunction1() {
public final void apply(String b) {
Test.this.Do2("a", b);
}
}.apply("b");
(Do3("a", _: String))("b")
new AbstractFunction1() {
public final void apply(String x$1) {
Test.this.Do3("a", x$1);
}
}.apply("b");
Run Code Online (Sandbox Code Playgroud)
在这里,Do2和Do3首先被部分应用,然后立即应用返回的函数。
结论:
我想说,Do2和Do3在生成的字节码中大部分是等效的。完整的应用程序会产生简单、廉价的方法调用。部分应用程序在调用者处生成匿名函数类。您使用什么变体主要取决于您想要传达的意图。
Do1总是创建一个立即函数对象,但在被调用的代码中这样做。如果您希望经常对该函数进行部分应用,那么使用此变体将减少您的代码大小,并且可能会更早地触发 JIT 编译器,因为相同的代码被更频繁地调用。完整的应用程序会更慢,至少在 JIT 编译器内联并随后消除各个调用站点的对象创建之前是这样。我不是这方面的专家,所以我不知道你是否可以期待这种优化。我最好的猜测是你可以,对于纯函数。