Как закрытия Scala преобразовываются к объектам Java?

Я в настоящее время смотрю на реализации закрытия на различных языках. Когда дело доходит до Scala, однако, я не могу найти любую документацию относительно того, как закрытие отображается на объектах Java.

Это хорошо документируется, что функции Scala отображаются на объектах FunctionN. Я предполагаю, что ссылка на свободную переменную закрытия должна быть сохранена где-нибудь в том функциональном объекте (поскольку это сделано в C++ 0x, например).

Я также пытался компилировать следующее с scalac и затем декомпилировать файлы класса с JD:

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

В декомпилируемых источниках я вижу анонимный подтип Function1, который должен быть моим закрытием. Но применение () метод пуст, и анонимный класс не имеет никаких полей (который мог потенциально сохранить переменные закрытия). Я предполагаю, что декомпилятору не удалось вытащить интересную часть из файлов класса...

Теперь к вопросам:

  • Вы знаете, как преобразование сделано точно?
  • Вы знаете, где это документируется?
  • У Вас есть другая идея, как я мог решить тайну?
18
задан theDmi 19 April 2010 в 19:49
поделиться

2 ответа

Давайте разберем набор примеров, чтобы мы могли увидеть, чем они отличаются. (При использовании 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) => i+m
}

Во-первых, давайте посмотрим, что делает метод :

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

Довольно просто. Это метод. Загрузите параметр, вызовите геттер для n , добавьте, верните. Похоже на Java.

Как насчет функции ? На самом деле она не закрывает никакие данные, но является анонимной функцией (называемой Close $$ anonfun $ function $ 1 ). Если мы игнорируем какую-либо специализацию, наибольший интерес представляют конструктор и apply:

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

Итак, вы загружаете указатель «this» и создаете новый объект, который принимает в качестве аргумента включающий класс. На самом деле это стандарт для любого внутреннего класса. Функция не должна ничего делать с внешним классом, поэтому она просто вызывает конструктор super. Затем, при вызове apply, вы выполняете трюки с коробкой / распаковкой, а затем вызываете фактическую математику, то есть просто добавляете 5.

Но что, если мы воспользуемся закрытием переменной внутри Close? Настройка точно такая же, но теперь конструктор Close $$ anonfun $ closure $ 1 выглядит так:

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

То есть он проверяет, не является ли ввод ненулевым (т.е. внешний класс не равно нулю) и сохраняет его в поле.Теперь, когда приходит время применить его, после обертки упаковки / распаковки:

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

вы видите, что он использует это поле для ссылки на родительский класс и вызывает геттер для n . Добавить, вернуть, готово. Итак, замыкания достаточно просты: конструктор анонимной функции просто сохраняет включающий класс в частном поле.

А как насчет того, чтобы закрыть не внутреннюю переменную, а аргумент метода? Это то, что делает Close $$ anonfun $ mixed $ 1 . Сначала посмотрите, что делает смешанный метод:

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

Он загружает параметр m перед вызовом конструктора! Поэтому неудивительно, что конструктор выглядит так:

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

где этот параметр сохраняется в частном поле. Ссылка на внешний класс не сохраняется, потому что она нам не нужна. И вас не должно удивлять применение apply:

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

Да, мы просто загружаем это сохраненное поле и выполняем вычисления.

Я не уверен, что вы делали, чтобы не увидеть этого в своем примере - объекты немного сложны, потому что у них есть классы MyObject и MyObject $ , а также методы разделитесь между двумя способами, которые могут быть не интуитивно понятными. Но apply определенно применяет некоторые вещи, и в целом вся система работает примерно так, как вы ожидаете (после того, как вы сядете и очень долго обдумаете это).

31
ответ дан 30 November 2019 в 07:28
поделиться

В отличие от анонимных внутренних классов Java, которые являются псевдозамкнутыми и не могут изменять переменные, которые кажутся закрытыми в своей среде, Scala's замыкания реальны, поэтому код закрытия напрямую ссылается на значения в окружающей среде. Такие значения компилируются по-разному, когда на них ссылаются из закрытия, чтобы сделать это возможным (поскольку у кода метода нет способа получить доступ к локальным переменным из любых фреймов активации, кроме текущего).

Напротив, в Java их значения копируются в поля внутреннего класса, поэтому язык требует, чтобы исходные значения в окружающей среде были окончательными , чтобы они никогда не расходились.

Поскольку все ссылки / замыкания функционального литерала Scala на значения во включающей среде находятся в коде метода функционального литерала apply () , они не отображаются как поля в фактическом Подкласс функции , созданный для функционального литерала.

Я не знаю, как вы декомпилируете, но подробности того, как вы это сделали, вероятно, объясняют, почему вы не видите никакого кода для тела метода apply () .

5
ответ дан 30 November 2019 в 07:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: