Is there a difference between a partial application and a function return?

In terms of the hood: stack / heap allocation, garbage collection, resources and performance, what is the difference between the following three:

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") 

In addition to the obvious differences in the properties of the declaration of how many arguments each element takes and returns

+5
source share
1 answer

Decompiling the following class (note the extra Do2 call compared to your question):

 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") } 

gives this clean 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"); } } 

(this code does not compile, but this is enough for analysis)


Take a look at this piece by piece (Scala and Java in each listing):

 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)); } }; } 

Regardless of how Do1 is Do1 , a new Function object is created.


 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 and Do3 compile to the same bytecode. The difference lies solely in the @ScalaSignature annotation.


 Do1("a")("b") Do1("a").apply("b"); 

Do1 straightforward: the return function is immediately applied.

 Do2("a")("b") Do2("a", "b"); 

With Do2 compiler sees that it is not a partial application, and compiles it with a single method call.


 (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"); 

Here Do2 and Do3 are applied first, then the returned functions are immediately applied.


Output:

I would say that Do2 and Do3 are basically equivalent in the generated bytecode. A complete application results in a simple cheap method call. A partial application generates anonymous function classes on the caller. Which option you use mainly depends on what intention you are trying to communicate with.

Do1 always creates an immediate function object, but does so in the called code. If you plan to partially run partial applications of this function, using this option will reduce the size of your code and possibly call the JIT compiler earlier, because the same code is called more often. The full application will be slower, at least until the JIT compiler includes the lines and subsequently eliminates the creation of objects on individual sites. I am not an expert in this, so I do not know whether such an optimization can be expected. My best guess was that you can, for pure functions.

+2
source

All Articles