对于数字密集型代码,我已经写了一个带有以下签名的函数:
def update( f: (Int,Int,Double) => Double ): Unit = {...}
但是,因为Function3不是专门的,所以f的每个应用都会导致打包/取消装箱3个参数和结果类型.
我可以使用一个特殊的更新类:
trait Updater { def apply( i: Int,j: Int,x: Double ): Double } def update( f: Updater ): Unit = {...}
但是调用很麻烦(和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,x: Double ) = if( i==0 || j ==0 ) 1.0 else 0.5 } )
是否有办法避免拳击/拆箱,同时仍然使用lambda语法?我希望宏将有所帮助,但我无法解决任何问题.
编辑:我用javap分析了function3生成的字节码.编译器根据通用方法(见下文)生成一个未装箱的方法.有没有办法直接打电话给未装箱的人?
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
解决方法
既然你提到了宏作为一个可能的解决方案,我的想法是编写一个匿名函数的宏,提取应用方法并将其插入一个扩展名为F3的自定义函数特征的匿名类.这是相当长的实现.
性状F3
trait F3[@specialized A,@specialized B,@specialized C,@specialized D] { def apply(a:A,b:B,c:C):D }
这个宏
implicit def function3toF3[A,B,C,D](f:Function3[A,D]):F3[A,D] = macro impl[A,D] def impl[A,D](c:Context)(f:c.Expr[Function3[A,D]]):c.Expr[F3[A,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() ),TypeTree(),Block( List( Apply( Select(Super(This(newTypeName("")),newTypeName("")),newTermName("<init>")),List() ) ),Literal(Constant(())) ) ),DefDef( Modifiers(Flag.OVERRIDE),newTermName("apply"),List(args),body ) ) ) ) ),Apply( Select( New( Ident(newTypeName("$anon")) ),nme.CONSTRUCTOR ),List() ) ) c.Expr[F3[A,D]](res) }
由于我将宏定义为隐式,所以可以这样使用:
def foo(f:F3[Int,Double,Double]) = { println(f.apply(1,2,3)) } foo((a:Int,b:Int,c:Double)=>a+b+c)
在调用foo之前,宏被调用,因为foo需要一个F3的实例.如预期的那样,对foo的调用打印“6.0”.现在我们来看看foo方法的反汇编,以确保不会发生拳击/拆箱:
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
这里完成的唯一的拳击是打印println.好极了!
最后一句话:在目前状态下,宏只适用于Int,Double的特殊情况,但可以很容易地修复.我把这个作为一个练习给读者.