Avoiding boxing/unboxing within function
About the anonymous class which extends Function3
(the bytecode of which you show) generated by Scalac - it is not possible to call the overloaded apply
with primitive parameters from within the b.update
, because that update
method takes a Function3
, which does not have an apply
with primitive parameters.
From within the Function3
bytecode, the only apply
is:
public abstract java.lang.Object apply(java.lang.Object, java.lang.Object, java.lang.Object);
You could instead use Function2[Long, Double]
, which is specialized on those types, and encode 2 integers x
and y
in a long as (x.toLong << 32) + y
, and decode them as v & 0xffffffff
and (v >> 32) & 0xffffffff
.
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 F3
trait F3[@specialized A, @specialized B, @specialized C, @specialized D] {
def apply(a:A, b:B, c:C):D
}
The macro
implicit def function3toF3[A,B,C,D](f:Function3[A,B,C,D]):F3[A,B,C,D] = macro impl[A,B,C,D]
def impl[A,B,C,D](c:Context)(f:c.Expr[Function3[A,B,C,D]]):c.Expr[F3[A,B,C,D]] = {
import c.universe._
var Function(args,body) = f.tree
args = args.map(c.resetAllAttrs(_).asInstanceOf[ValDef])
body = c.resetAllAttrs(body)
val res =
Block(
List(
ClassDef(
Modifiers(Flag.FINAL),
newTypeName("$anon"),
List(),
Template(
List(
AppliedTypeTree(Ident(c.mirror.staticClass("mcro.F3")),
List(
Ident(c.mirror.staticClass("scala.Int")),
Ident(c.mirror.staticClass("scala.Int")),
Ident(c.mirror.staticClass("scala.Double")),
Ident(c.mirror.staticClass("scala.Double"))
)
)
),
emptyValDef,
List(
DefDef(
Modifiers(),
nme.CONSTRUCTOR,
List(),
List(
List()
),
TypeTree(),
Block(
List(
Apply(
Select(Super(This(newTypeName("")), newTypeName("")), newTermName("<init>")),
List()
)
),
Literal(Constant(()))
)
),
DefDef(
Modifiers(Flag.OVERRIDE),
newTermName("apply"),
List(),
List(args),
TypeTree(),
body
)
)
)
)
),
Apply(
Select(
New(
Ident(newTypeName("$anon"))
),
nme.CONSTRUCTOR
),
List()
)
)
c.Expr[F3[A,B,C,D]](res)
}
Since I defined the macro as implicit, it can be used like this:
def foo(f:F3[Int,Int,Double,Double]) = {
println(f.apply(1,2,3))
}
foo((a:Int,b:Int,c:Double)=>a+b+c)
Before foo is called, the macro is invoked because foo
expects an instance of F3
. As expected, the call to foo
prints "6.0". Now let's look at the disassembly of the foo
method, to make sure that no boxing/unboxing takes place:
public void foo(mcro.F3);
Code:
Stack=6, Locals=2, Args_size=2
0: getstatic #19; //Field scala/Predef$.MODULE$:Lscala/Predef$;
3: aload_1
4: iconst_1
5: iconst_2
6: ldc2_w #20; //double 3.0d
9: invokeinterface #27, 5; //InterfaceMethod mcro/F3.apply$mcIIDD$sp:(IID)D
14: invokestatic #33; //Method scala/runtime/BoxesRunTime.boxToDouble:(D)Ljava/lang/Double;
17: invokevirtual #37; //Method scala/Predef$.println:(Ljava/lang/Object;)V
20: return
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,Double
but that can easily be fixed. I leave that as an exercise to the reader.