部分应用和返回函数之间有区别吗?
Is there a difference between partial application and returning a function?
就底层而言:stack/heap分配、垃圾收集、资源和性能,以下三者之间有什么区别:
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")
除了声明中关于每个参数的明显表面差异和 returns
反编译以下 class(注意与您的问题相比,对 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")
}
产生这个纯 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) {
Test.this.Do3("a", x);
}
}.apply("b");
}
}
(这段代码编译不过,但足够分析)
让我们逐个看一下(每个清单中的 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));
}
};
}
无论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));
}
Do2
和 Do3
编译成相同的字节码。区别仅在于 @ScalaSignature
注释。
Do1("a")("b")
Do1("a").apply("b");
Do1
很简单:立即应用返回的函数。
Do2("a")("b")
Do2("a", "b");
对于 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) {
Test.this.Do3("a", x);
}
}.apply("b");
这里先部分应用Do2
和Do3
,然后立即应用返回的函数。
结论:
我会说 Do2
和 Do3
在生成的字节码中大部分是等价的。完整的应用程序会导致简单、廉价的方法调用。部分应用程序在调用方生成匿名函数 classes。您使用哪种变体主要取决于您试图传达的意图。
Do1
总是创建一个立即函数对象,但在被调用的代码中这样做。如果您希望对函数进行大量的部分应用,使用此变体将减少您的代码大小,并且可能更早地触发 JIT 编译器,因为相同的代码被更频繁地调用。完整的应用程序会变慢,至少在 JIT 编译器内联并随后消除各个调用站点的对象创建之前是这样。我不是这方面的专家,所以我不知道您是否可以期待这种优化。对于纯函数,我最好的猜测是你可以。
就底层而言:stack/heap分配、垃圾收集、资源和性能,以下三者之间有什么区别:
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")
除了声明中关于每个参数的明显表面差异和 returns
反编译以下 class(注意与您的问题相比,对 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")
}
产生这个纯 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) {
Test.this.Do3("a", x);
}
}.apply("b");
}
}
(这段代码编译不过,但足够分析)
让我们逐个看一下(每个清单中的 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));
}
};
}
无论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));
}
Do2
和 Do3
编译成相同的字节码。区别仅在于 @ScalaSignature
注释。
Do1("a")("b")
Do1("a").apply("b");
Do1
很简单:立即应用返回的函数。
Do2("a")("b")
Do2("a", "b");
对于 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) {
Test.this.Do3("a", x);
}
}.apply("b");
这里先部分应用Do2
和Do3
,然后立即应用返回的函数。
结论:
我会说 Do2
和 Do3
在生成的字节码中大部分是等价的。完整的应用程序会导致简单、廉价的方法调用。部分应用程序在调用方生成匿名函数 classes。您使用哪种变体主要取决于您试图传达的意图。
Do1
总是创建一个立即函数对象,但在被调用的代码中这样做。如果您希望对函数进行大量的部分应用,使用此变体将减少您的代码大小,并且可能更早地触发 JIT 编译器,因为相同的代码被更频繁地调用。完整的应用程序会变慢,至少在 JIT 编译器内联并随后消除各个调用站点的对象创建之前是这样。我不是这方面的专家,所以我不知道您是否可以期待这种优化。对于纯函数,我最好的猜测是你可以。