For a numeric intensive code I have written a function with the following signature:
def update( f: (Int,Int,Double) => Double ): Unit = {...}
However, because Function3 is not specialized, every application of f results in boxing/unboxing the 3 arguments and the result type.
I could use a special updater class:
trait Updater {
def apply( i: Int, j: Int, x: Double ): Double
}
def update( f: Updater ): Unit = {...}
But the invocation is cumbersome (and java-ish):
//with function
b.update( (i,j,_) => if( i==0 || j ==0 ) 1.0 else 0.5 )
//with updater
b.update( new Updater {
def apply( i: Int, j: Int, x: Double ) = if( i==0 || j ==0 ) 1.0 else 0.5
} )
Is there a way to avoid boxing/unboxing while still using the lambda syntax ? I was hoping macros will help, but I cannot figure any solution.
EDIT: I analyzed the function3 generated byte code with javap. An unboxed method is generated by the compiler, along the generic method (see below). Is there a way to call the unboxed one directly ?
public final double apply(int, int, double);
Code:
0: ldc2_w #14; //double 100.0d
3: iload_2
4: i2d
5: dmul
6: iload_1
7: i2d
8: ddiv
9: dreturn
public final java.lang.Object apply(java.lang.Object, java.lang.Object, java.lang.Object);
Code:
0: aload_0
1: aload_1
2: invokestatic #31; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
5: aload_2
6: invokestatic #31; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
9: aload_3
10: invokestatic #35; //Method scala/runtime/BoxesRunTime.unboxToDouble:(Ljava/lang/Object;)D
13: invokevirtual #37; //Method apply:(IID)D
16: invokestatic #41; //Method scala/runtime/BoxesRunTime.boxToDouble:(D)Ljava/lang/Double;
19: areturn
Since you mentioned macros as a possible solution, I got the idea of writing a macro that takes an anonymous function, extracts the apply methods and inserts it into an anonymous class that extends a custom function trait called
F3. This is the quite long implementation.The trait
F3The macro
Since I defined the macro as implicit, it can be used like this:
Before foo is called, the macro is invoked because
fooexpects an instance ofF3. As expected, the call tofooprints “6.0”. Now let’s look at the disassembly of thefoomethod, to make sure that no boxing/unboxing takes place:The only boxing that is done here is for the call to
println. Yay!One last remark: In its current state, the macro only works for the special case of
Int,Int,Double,Doublebut that can easily be fixed. I leave that as an exercise to the reader.