如何将Scala闭包转换为Java对象? [英] How are Scala closures transformed to Java objects?

查看:130
本文介绍了如何将Scala闭包转换为Java对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在使用不同语言的闭包实现。然而,谈到Scala,我找不到任何关于如何映射到Java对象的文档。



有很多文档说明了Scala函数映射到FunctionN对象。我假定对闭包的自由变量的引用必须存储在该函数对象中的某处(如在C ++ 0x中所做的那样)。



我也尝试用scalac编译以下代码,然后用JD反编译类文件:

  object ClosureExample extends应用程序{
def addN(n:Int)=(a:Int)=> a + n
var add5 = addN(5)
println(add5(20))
}

在反编译的源代码中,我看到一个匿名的Function1子类型,它应该是我的闭包。但是apply()方法是空的,并且匿名类没有字段(可能存储闭包变量)。我假设反编译器没有设法从类文件中获取有趣的部分...

现在到问题:




b $ b

  • 你知道转换是如何完成的吗?

  • 你知道它在哪里记录吗?

  • 您有另一个想法如何解决这个秘密?


解决方案

分开一组例子,我们可以看到它们之间的区别。 (如果使用RC1,使用 -no-specialization 编译以使事情更容易理解。)

  class Close {
var n = 5
def method(i:Int)= i + n
def function =(i:Int)=& i + 5
def closure =(i:Int)=> i + n
def mixed(m:Int)=(i:Int)=>首先,让我们看看什么方法<$ c $ b> / code> does:

  public int method 
代码:
0:iload_1
1:aload_0
2:invokevirtual#17; //方法n :()I
5:iadd
6:ireturn

相当简单。这是一种方法。加载参数,调用 n 的getter,添加,返回。看起来就像Java。



function 如何?它实际上并不关闭任何数据,但它是一个匿名函数(称为关闭$$ anonfun $函数$ 1 )。如果我们忽略任何专门化,构造函数和应用是最感兴趣的:

  public scala.Function1 function 
代码:
0:new#34; // class Close $$ anonfun $ function $ 1
3:dup
4:aload_0
5:invokespecial#35; //方法关闭$$ anonfun $ function $ 1。< init>:( LClose;)V
8:areturn

public Close $$ anonfun $ function $ 1
代码:
0:aload_0
1:invokespecial#43; //方法scala / runtime / AbstractFunction1。< init>:()V
4:return

public final java.lang.Object apply(java.lang.Object);
代码:
0:aload_0
1:aload_1
2:invokestatic#26; //方法scala / runtime / BoxesRunTime.unboxToInt:(Ljava / lang / Object;)I
5:invokevirtual#28; //方法适用:(I)I
8:invokestatic#32; //方法scala / runtime / BoxesRunTime.boxToInteger:(I)Ljava / lang / Integer;
11:areturn

public final int apply(int);
代码:
0:iload_1
1:iconst_5
2:iadd
3:ireturn

所以,你加载一个this指针,并创建一个新的对象,将包围类作为其参数。这是任何内部类的标准,真的。该函数不需要对外部类做任何事情,所以它只是调用super的构造函数。然后,当调用apply时,你执行box / unbox技巧,然后调用实际的数学 - 即只需添加5。



但是如果我们使用闭包的变量里面关闭?设置完全相同,但现在构造函数关闭$$ anonfun $ closure $ 1 如下所示:

  public Close $$ anonfun $ closure $ 1(Close); 
代码:
0:aload_1
1:ifnonnull 12
4:new#48; // class java / lang / NullPointerException
7:dup
8:invokespecial#50; //方法java / lang / NullPointerException。< init>:()V
11:athrow
12:aload_0
13:aload_1
14:putfield# // Field $ outer:LClose;
17:aload_0
18:invokespecial#53; //方法scala / runtime / AbstractFunction1。< init>:()V
21:return

也就是说,它检查以确保输入是非空的(即外部类是非空的)并将其保存在字段中。现在当它到了应用它的时候,在拳击/开箱包装后:

  public final int apply 
代码:
0:iload_1
1:aload_0
2:getfield#18; // Field $ outer:LClose;
5:invokevirtual#24; // Method Close.n :()I
8:iadd
9:ireturn

您会看到它使用该字段引用父类,并调用 n 的getter。添加,返回,完成。所以,闭包很容易:匿名函数构造函数只是将一个封闭类保存在一个私有字段中。



现在,如果我们关闭不是一个内部变量,但是一个方法参数?这就是关闭$$ anonfun $ mixed $ 1 。首先,看看混合方法的作用:

  public scala。 function1 mixed(int); 
代码:
0:new#39; // class Close $$ anonfun $ mixed $ 1
3:dup
4:aload_0
5:iload_1
6:invokespecial#42; // Method Close $$ anonfun $ mixed $ 1。< init>:( LClose; I)V
9:areturn

它在调用构造函数之前加载参数 m !所以这并不奇怪,构造函数看起来像这样:

  public关闭$$ anonfun $ mixed $ 1 
代码:
0:aload_0
1:iload_2
2:putfield#18; // Field m $ 1:I
5:aload_0
6:invokespecial#43; // Method scala / runtime / AbstractFunction1。< init>:()V
9:return

其中该参数保存在专用字段中。不保留对外部类的引用,因为我们不需要它。你不应该惊讶于应用:

  public final int apply(int); 
代码:
0:iload_1
1:aload_0
2:getfield#18; // Field m $ 1:I
5:iadd
6:ireturn

是的,我们只是加载那个存储字段,并做我们的数学。



我不知道你在做什么,没有看到这个与你的示例 - 对象有点棘手的,因为他们都有 MyObject MyObject $ 类和方法在两者之间拆分,直观。但应用绝对适用的东西,整体的整个系统的工作方式,你想要的方式(在你坐下来,认为它真的很难很长一段时间)。


I'm currently looking at closure implementations in different languages. When it comes to Scala, however, I'm unable to find any documentation on how a closure is mapped to Java objects.

It is well documented that Scala functions are mapped to FunctionN objects. I assume that the reference to the free variable of the closure must be stored somewhere in that function object (as it is done in C++0x, e.g.).

I also tried compiling the following with scalac and then decompiling the class files with JD:

object ClosureExample extends Application { 
  def addN(n: Int) = (a: Int) => a + n
  var add5 = addN(5)
  println(add5(20))
}

In the decompiled sources, I see an anonymous subtype of Function1, which ought to be my closure. But the apply() method is empty, and the anonymous class has no fields (which could potentially store the closure variables). I suppose the decompiler didn't manage to get the interesting part out of the class files...

Now to the questions:

  • Do you know how the transformation is done exactly?
  • Do you know where it is documented?
  • Do you have another idea how I could solve the mystery?

解决方案

Let's take apart a set of examples so we can see how they differ. (If using RC1, compile with -no-specialization to keep things easier to understand.)

class Close {
  var n = 5
  def method(i: Int) = i+n
  def function = (i: Int) => i+5
  def closure = (i: Int) => i+n
  def mixed(m: Int) = (i: Int) => i+m
}

First, let's see what method does:

public int method(int);
  Code:
   0:   iload_1
   1:   aload_0
   2:   invokevirtual   #17; //Method n:()I
   5:   iadd
   6:   ireturn

Pretty straightforward. It's a method. Load the parameter, invoke the getter for n, add, return. Looks just like Java.

How about function? It doesn't actually close any data, but it is an anonymous function (called Close$$anonfun$function$1). If we ignore any specialization, the constructor and apply are of most interest:

public scala.Function1 function();
  Code:
   0:   new #34; //class Close$$anonfun$function$1
   3:   dup
   4:   aload_0
   5:   invokespecial   #35; //Method Close$$anonfun$function$1."<init>":(LClose;)V
   8:   areturn

public Close$$anonfun$function$1(Close);
  Code:
   0:   aload_0
   1:   invokespecial   #43; //Method scala/runtime/AbstractFunction1."<init>":()V
   4:   return

public final java.lang.Object apply(java.lang.Object);
  Code:
   0:   aload_0
   1:   aload_1
   2:   invokestatic    #26; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
   5:   invokevirtual   #28; //Method apply:(I)I
   8:   invokestatic    #32; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
   11:  areturn

public final int apply(int);
  Code:
   0:   iload_1
   1:   iconst_5
   2:   iadd
   3:   ireturn

So, you load a "this" pointer and create a new object that takes the enclosing class as its argument. This is standard for any inner class, really. The function doesn't need to do anything with the outer class so it just calls the super's constructor. Then, when calling apply, you do the box/unbox tricks and then call the actual math--that is, just add 5.

But what if we use a closure of the variable inside Close? Setup is exactly the same, but now the constructor Close$$anonfun$closure$1 looks like this:

public Close$$anonfun$closure$1(Close);
  Code:
   0:   aload_1
   1:   ifnonnull   12
   4:   new #48; //class java/lang/NullPointerException
   7:   dup
   8:   invokespecial   #50; //Method java/lang/NullPointerException."<init>":()V
   11:  athrow
   12:  aload_0
   13:  aload_1
   14:  putfield    #18; //Field $outer:LClose;
   17:  aload_0
   18:  invokespecial   #53; //Method scala/runtime/AbstractFunction1."<init>":()V
   21:  return

That is, it checks to make sure that the input is non-null (i.e. the outer class is non-null) and saves it in a field. Now when it comes time to apply it, after the boxing/unboxing wrapper:

public final int apply(int);
  Code:
   0:   iload_1
   1:   aload_0
   2:   getfield    #18; //Field $outer:LClose;
   5:   invokevirtual   #24; //Method Close.n:()I
   8:   iadd
   9:   ireturn

you see that it uses that field to refer to the parent class, and invokes the getter for n. Add, return, done. So, closures are easy enough: the anonymous function constructor just saves the enclosing class in a private field.

Now, what about if we close not an internal variable, but a method argument? That's what Close$$anonfun$mixed$1 does. First, look at what the mixed method does:

public scala.Function1 mixed(int);
  Code:
   0:   new #39; //class Close$$anonfun$mixed$1
   3:   dup
   4:   aload_0
   5:   iload_1
   6:   invokespecial   #42; //Method Close$$anonfun$mixed$1."<init>":(LClose;I)V
   9:   areturn

It loads the parameter m before calling the constructor! So it's no surprise that the constructor looks like this:

public Close$$anonfun$mixed$1(Close, int);
  Code:
   0:   aload_0
   1:   iload_2
   2:   putfield    #18; //Field m$1:I
   5:   aload_0
   6:   invokespecial   #43; //Method scala/runtime/AbstractFunction1."<init>":()V
   9:   return

where that parameter is saved in a private field. No reference to the outer class is kept because we don't need it. And you ought not be surprised by apply either:

public final int apply(int);
  Code:
   0:   iload_1
   1:   aload_0
   2:   getfield    #18; //Field m$1:I
   5:   iadd
   6:   ireturn

Yes, we just load that stored field and do our math.

I'm not sure what you were doing to not see this with your example--objects are a little tricky because they have both MyObject and MyObject$ classes and the methods get split between the two in a way that may not be intuitive. But apply definitely applies things, and overall the whole system works pretty much the way you'd expect it to (after you sit down and think about it really hard for a really long time).

这篇关于如何将Scala闭包转换为Java对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆