2010-04-19 3 views
18

В настоящее время я смотрю на реализации закрытия на разных языках. Однако, когда дело доходит до 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, что должно быть мое закрытием. Но метод apply() пуст, а анонимный класс не имеет полей (которые потенциально могут хранить переменные замыкания). Я полагаю, что декомпилятор не удалось получить интересную часть из файлов класса ...

Теперь вопросы:

  • Вы знаете, как преобразование делается именно?
  • Знаете ли вы, где они задокументированы?
  • У вас есть еще одна идея, как я могу решить эту тайну?
+1

Вы действительно используете 'javap -c 'ClassName $$ anonfun $ etcetc'' для декомпиляции? –

ответ

31

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

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

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

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

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

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 

Итак, вы загрузили «это» указатель и создать новый объект, который принимает класс ограждающую в качестве аргумента. Это действительно стандарт для любого внутреннего класса. Функция не должна ничего делать с внешним классом, поэтому она просто вызывает конструктор супер. Затем, когда вы набираете заявку, вы делаете тэки box/unbox, а затем вызываете фактическую математику, то есть просто добавляете 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. Во-первых, обратите внимание на то, что делает метод mixed:

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 final int apply(int); 
    Code: 
    0: iload_1 
    1: aload_0 
    2: getfield #18; //Field m$1:I 
    5: iadd 
    6: ireturn 

Да, мы просто загрузить сохраненную поле и сделать нашу математику.

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

+0

Прекрасный ответ! –

+0

Большое спасибо, это очень подробно. Я пытался использовать декомпилятор, который восстанавливает Java-код (он называется JD), потому что я не привык к байт-коду, и это не сработало.Я думаю, что декомпилятор неправильно обрабатывает анонимные классы. Теперь, когда я изучил основы байт-кода Java, ваш ответ легко понять. Вы знаете какую-либо официальную документацию об этом преобразовании? – theDmi

+0

@iguana: Я не знаю нигде, что трансформация документирована подробно. В любом случае я склонен считать байт-код более чем документами. –

5

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

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

Поскольку все функции Scala литерала/ссылка ЗАКРЫТИЯ к ценностям в окружающей среде, вмещающей в коде функции apply() метода литерала, они не отображаются в виде полей в фактическом Function подкласса сгенерирован для функции буквальной.

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

+0

Спасибо, это то, что я подозревал. Я еще раз исследую проблему декомпиляции. У вас есть официальные источники, где это поведение документировано? Это было бы очень полезно, потому что мне нужно что-то, что я могу назвать :-). – theDmi

+0

Ничто из того, что я написал, не является авторитетным. Полагаю, я должен был сказать, что ... Существует компилятор Scala Corner (http://www.sts.tu-harburg.de/people/mi.garcia/ScalaCompilerCorner/), который является хорошим ресурсом и может содержать такой информации. –

Смежные вопросы