14

数値を多用するコードの場合、次のシグネチャを使用して関数を記述しました。

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っぽい):

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

ラムダ構文を使用しながら、ボクシング/アンボクシングを回避する方法はありますか?マクロが役立つことを望んでいましたが、解決策がわかりません。

編集:私は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
4

3 に答える 3

6

考えられる解決策としてマクロについて言及したので、無名関数を受け取り、applyメソッドを抽出して、と呼ばれるカスタム関数特性を拡張する匿名クラスに挿入するマクロを作成することを思いつきました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,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)
  }

マクロを暗黙的に定義したので、次のように使用できます。

def foo(f:F3[Int,Int,Double,Double]) = {
  println(f.apply(1,2,3))
}

foo((a:Int,b:Int,c:Double)=>a+b+c)

foofooが呼び出される前に、のインスタンスが必要なため、マクロが呼び出されます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です。わーい!

最後にもう1つ注意してください。現在の状態では、マクロはの特殊なケースでのみ機能しInt,Int,Double,Doubleますが、簡単に修正できます。私はそれを読者の練習問題として残します。

于 2012-08-23T15:58:31.123 に答える
2

Function3Scalacによって生成された拡張する匿名クラス(表示するバイトコード)について-そのメソッドは、プリミティブパラメーターを持たないをとるので、applyプリミティブパラメーターでオーバーロードされたものを呼び出すことはできません。b.updateupdateFunction3apply

Function3バイトコード内から、唯一applyは次のとおりです。

public abstract java.lang.Object apply(java.lang.Object, java.lang.Object, java.lang.Object);

代わりにFunction2[Long, Double]、これらのタイプに特化したを使用し、2つの整数xylongをエンコードし、それらをと(x.toLong << 32) + yとしてデコードすることができます。v & 0xffffffff(v >> 32) & 0xffffffff

于 2012-08-23T09:31:29.720 に答える
0

専門としてFunction1、考えられる解決策は、カリー化を使用し、更新方法を次のように変更することです。

def update( f: Int => Int => Double => Double ): Unit = {...}

それに応じてインライン関数を変更します。あなたの例で(updateすぐにテストするために少し変更されました):

scala> def update( f: Int => Int => Double => Double ): Double = f(1)(2)(3.0)
update: (f: Int => (Int => (Double => Double)))Double

scala> update(i => j => _ => if (i == 0 && j == 0) 1.0 else 0.5)
res1: Double = 0.5

編集:コメントで説明されているように、最初のパラメーターはまだボックス化されているため、完全には役に立ちません。私はそれについての痕跡を残すために答えを残します。

于 2012-08-23T09:44:14.520 に答える