Decompiling die folgende Klasse (beachten Sie den zusätzlichen Aufruf Do2
im Vergleich zu Ihrer Frage):
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")
}
ergibt diese reine Java-Code:
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");
}
}
(dieser Code nicht kompiliert, sondern es reicht für die Analyse)
ist es Teil für Teil (Scala & Java in jeder Auflistung) Schauen wir uns:
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));
}
};
}
Unabhängig davon, wie Do1
aufgerufen wird, wird ein neues Funktionsobjekt erstellt.
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
und Do3
auf den gleichen Bytecode kompiliert unten. Der Unterschied liegt ausschließlich in der @ScalaSignature
Annotation.
Do1("a")("b")
Do1("a").apply("b");
Do1
ist geradlinig: die zurückgegebene Funktion sofort angewendet wird.
Do2("a")("b")
Do2("a", "b");
Mit Do2
sieht der Compiler, dass dies nicht eine Teilanmeldung ist, und stellt es auf einen einzigen Methodenaufruf.
(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");
Hier Do2
und Do3
sind zunächst teilweise angewendet wird, dann werden die zurückgegebenen Funktionen sofort angewendet.
Fazit:
Ich würde sagen, dass Do2
und Do3
in der erzeugten Bytecode meist gleichwertig sind. Eine vollständige Anwendung führt zu einem einfachen, kostengünstigen Methodenaufruf. Teilapplikation erzeugt beim Aufrufer anonyme Funktionsklassen. Welche Variante Sie verwenden, hängt hauptsächlich davon ab, welche Absicht Sie kommunizieren möchten.
Do1
erstellt immer ein sofortiges Funktionsobjekt, tut dies jedoch im aufgerufenen Code. Wenn Sie erwarten, dass Sie die Funktion teilweise ausführen, reduziert die Verwendung dieser Variante Ihre Codegröße und löst möglicherweise den JIT-Compiler früher aus, da der gleiche Code häufiger aufgerufen wird.Die vollständige Anwendung wird zumindest vor den Inline-Anweisungen des JIT-Compilers langsamer und beseitigt anschließend die Erstellung von Objekten an einzelnen Call-Sites. Ich bin kein Experte darin, also weiß ich nicht, ob Sie diese Art der Optimierung erwarten können. Meine beste Vermutung wäre, dass Sie für reine Funktionen können.
Nun, das hat nichts mit Currying zu tun. In jedem Fall ging es um die Frage unter den Motorhauben. – Alex
Entschuldigung, ich habe den wichtigen Teil Ihrer Frage übersehen: wie es sich auf die Speicherzuweisung bezieht. Ich denke immer noch, dass es eine Menge relevanter Informationen in der verwandten Frage gibt, aber es ist kein Duplikat, du hast Recht. –
Ich würde auf einer Bytecode-Ebene schätzen, dass 'Do2' und' Do3' gleich sind. Ihr Aufruf an 'Do2' ist wahrscheinlich ein einfacher Methodenaufruf, während ich erwarten würde, dass der 'Do3'-Aufruf ein Zwischenfunktionsobjekt erzeugt. 'Do1' sollte das trotzdem tun. Vielleicht könnte jetzt jemand mit mehr Zeit einen 'javap' machen und es als Antwort aufschreiben. –