Pull to refresh

Comments 19

Полагаю, «правильным» определением все же стоит считать то определение, которое появилось первым. Замыкания первыми появились в LISP, и есть отличное определение, которое дал Пол Грем: «When a function refers to a variable defined outside it, it's called a free variable. A function that refers to a free lexical variable is called a closure» Мне кажется, данное определение можно рассматривать как единственно верное по двум причинам: оно основывается на LISP, одном из старейших функциональных языков и такое же определение встречается в JS. Соответственно исходя из такого определения считать лямбды в Java замыканиями ошибочно, но это так же не означает, что лямбды в java реализованы как то хуже — они позволяют делать практически те же вещи, что и замыкания, при учете того, что у final объекта есть внутренне состояние, которое можно изменить с помошью его методов до и после определения самой лямбды.
Хм, а вот ошибочно ли? Если вы говорите про то, что effectively final variable на самом деле не variable, а value, то разве второе не является просто частным случаем первого?
Тут вы меня поставили честно говоря в тупик. Здесь есть два важных момента:
1. free variable — это некая переменная которая не является ни локальной переменной, ни аргументом функции
2. final variable объявленная до лямбды и использующаяся в лямбде является локальной переменной
Допустим у нас есть следующий код:
public class Main {

    @FunctionalInterface
    interface FA {
        void doCall();
    }

    public static void main(String[] args) {
        final Integer I = new Integer(300);
        FA fa = () -> {
            System.out.println(I);
        };
    }
}

Скомпилируем этот класс и посмотрим в байт код лямбды, мы увидим что то такое:
  private static synthetic lambda$main$0(Ljava/lang/Integer;)V
   L0
    LINENUMBER 11 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 0
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L1
    LINENUMBER 12 L1
    RETURN
   L2
    LOCALVARIABLE I Ljava/lang/Integer; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1


Самая интересная строчка тут: LOCALVARIABLE I Ljava/lang/Integer; L0 L2 0

Возможно я не прав и ничего не понимаю в bytecode, но тут вроде как локальная переменная, а значит не свободная. И это ведь и логично, в Java мы не можем сделать по другому, не изменив спецификацию JVM, а там, если мне память не изменяет сказано, что для лямбд значение финальной ссылки будет _скопировано_ в тело, и финальные и не финальные пеерменные с точки зрения bytecode вообще не отличимы.
Смотря с какой стороны посмотреть. Компилируется оно, действительно, в локальную переменную.

Но вот с точки зрения языка, как мне кажется, это все-таки свободная переменная. Банально, важным свойством локальной переменной является то, что ее не существует за пределами данного скоупа. Однако код
    public void foo() {
        int a = 42;

        Runnable r = () -> {
            System.out.println(a);
        };

        System.out.println(a);
    }

компилируется. Т.е. область видимости a шире функции.
Язык это не только синтаксис, язык это его синтаксис и компилятор/интерпритатор(можно бесконечно обсуждать созданные в уме языки программирвоания, но пока у них нет хотя бы одного компилятора — это просто пустая болтовня). И ваш пример совершенно больше демонстрирует то, что здесь имеют место быть 2 переменные с именем a, каждая из которых указывает на один и тот же участок памяти. При создании лямбды ссылка копируется, тем самым a внутри лямбды уже не свободная переменная. И это отличие является причиной необходимости делать final/effectively final внешнюю переменную. Поэтому да, в Java 8 есть лямбды, но нет замыканий. Реализация лямбд близка к реализации замыканий на столько, на сколько это возможно, но вводить энваромент уровня функции или еще какой нибудь способ реализации подобного функционала — на данный моментв JVM не возможен.
Я бы все-таки различал язык и платформу. В платформе Java замыканий нет (т.к. все копируется и внешних ссылок не остается). А вот языке Java переменная a таки одна.
Вот тут член команды BGGA Нил Гафтер поясняет отличия замыкания от анонимного класса(комментарии датированы 2009 годом) и вот что он пишет:
That binds to a constant, not a variable. The point is to use an identifier in the body of the lambda expression, and have that bound to a lexically enclosing variable. That is one of the defining features of closures, and the facility you describe lacks it.

Если рассуждать с точки зрения языка, а не с точки зрения компилятора, то любая константа используемая вне своего определения может быть замена своим значением прямо в коде программы.
В таком случае в Java8 по попросту нет возможности создать замыкание в коде, потому что все свободные переменные будут заменены подстановками, так как они по сути — константы. Тогда, я могу сделать следующий вывод:
В Java8 замыканий нет, так как не возможно написать такую конструкцию, которая бы была бы замыканием, а не лямбдой.
Любопытная ссылка, спасибо. Но позвольте не согласится. Вы утверждаете что константа не может являться свободной переменной. Однако, в оригинальном лямбда-исчислении (а само понятие «лямбда» вообще-то оттуда и пришло) никаких требований на возможность изменения нет. Более того, там изменяемых переменных и не существует даже. Но это не мешает существованию свободных переменных.
Ну на самом деле есть,
The set of free variables in an expression E,
denoted by FV(E), is defined by:
a) FV( c ) = ∅ for any constant c
b) FV(x) = {x} for any variable x
c) FV(E1 E2) = FV(E1) ∪ FV(E2)
d) FV(λx. E) = FV(E) – {x}


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

Более того, там изменяемых переменных и не существует даже.

Разница между не изменяемой переменной и константой в рамках лямбда исчисления можно представить следующим образом: константа определена вообще в любой момент времени, а вот не изменяеммая переменная только с момента «захвата»(capturing). То есть однажды получив значение, мы можем везде использовать подстановки вида:
λx[x := r]. x = r, до момента «захвата» — нет, не можем.
Брр. Уже второй раз за последнее время использую слово «константа» в значении «локально неизменяемая». За что, закономерно, и огребаю. Надо отвыкать.

Пункт a явно показывает, что множество свободных переменных для любой константы — пустое множество
Безусловно. Что «у константы нет свободных переменных» — это очевидно. Я говорил что «сама константа может являться свободной переменной». И это с поправкой, что константа — не литерал «42», а переменная «a = 42», которую мы где-то раньше объявили, но не изменяем.

На самом деле, в лямбда-исчисление я, возможно, зря полез, т.к. хоть там и есть термин «свободная переменная», но вот термина «замыкание», на сколько мне известно, нет.

Ок, давайте на примере. Рассмотрим терм λaf.fa. Так вот, разве его подтерм λf.fa не является замыканием? Он ведь содержит свободное вхождение переменной a. Но это в чистом виде джавовая лямбда/анонимный класс:
    public Function<Function<Integer, Integer>, Integer> foo(Integer a) {
        return f -> f.apply(a);
    }


почти оффтоп
Изначально хотел чтобы типы были попроще и возвращался просто Supplier<Integer>, но не смог написать терм для "() -> a". Ничего кроме просто «a» придумать не получилось, но в чем тогда отличие между «a» и "() -> a". На сколько я помню, абстрагироваться обязательно нужно по какой-то переменной. Т.е. терм "λ.a" некорректен.
UFO just landed and posted this here
λb.a «съедает» один аргумент. Т.е. это (b) -> a. Но вообще да, a и () -> a в чистом лямбда-исчислении явно являются одним и тем же.
Ну можно считать, что effectively final переменная это никакая не переменная, а открытие нового скоупа, записанное таким странным способом (и соответственно, на каждый чих заставляющее плодить мусорные скоупы).

В качестве примера, можно вместо просто строчки «int a» (которое по вашей логике не free), записать AtomicReference[Integer] a, и теперь a.get() уже free.
но a.get() — не переменная, все же вызов функции и некая переменная совсем разные вещи.
ну так джава целиком на паттернах, в ней есть многое того, чего в ней нет) Глобальные переменные называются синглтонами, паттерн-матчинги — визитором, замыкания — анонимными внутренними классами, итп :))) То есть на джава-код надо смотреть не на предмет что там написано буквально, а что там происходит на самом деле
Как-то из внешенго мира вопрос в заголовке выглядит как теплое с мягким. Лямбда-функция — это неименованная функция, которую можно объявить в теле метода. Замыкания — возможность использовать перменные, внешние для объявленной функции. Соотвественно, лямбда-функции могут использовать механизм замыканий, а могут и нет — и поддержка замыканий для лямбда функций может быть в языке, а может и нет. Конечно, все эти три вещи идут достаточно близко — лямбда-функции, функции как объект первого порядка (грубо говоря, переменные типа «функции») и замыкания. Язык может не поддерживать лямбда-функции — именно как неименованные функции, но уметь замыкания и наоборот.
UFO just landed and posted this here

Почему здесь в примерах принтов (что питон, что java) передается два аргумента, а в примере вывода показано три?

Первые два выводится в лямбде. Третий выводится в коде вызывающем эту лямбду.

Sign up to leave a comment.