Java нуждается в замыканиях? - PullRequest
44 голосов
/ 08 сентября 2008

В последнее время я много читал о следующем выпуске Java, возможно, поддерживающем замыкания . Я чувствую, что у меня есть достаточно твердое понимание того, что такое замыкания, но я не могу придумать убедительный пример того, как они сделали бы объектно-ориентированный язык «лучше». Может ли кто-нибудь дать мне конкретный вариант использования, где понадобится закрытие (или даже предпочтение)?

Ответы [ 19 ]

70 голосов
/ 04 апреля 2009

Как программист на Лиспе, я бы хотел, чтобы сообщество Java понимало следующую разницу: функционирует как объект против замыканий .

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

Анонимные функции мало что добавляют в язык, они просто позволяют вам писать функции более коротким способом.

b) Закрытие - это функция плюс среда привязки . Замыкания могут быть переданы вниз (в качестве параметров) или возвращены вверх (в качестве возвращаемых значений). Это позволяет функции ссылаться на переменные своего окружения, даже если окружающий код больше не активен.

Если у вас есть a) на каком-либо языке, то возникает вопрос, что делать с b) ? Есть языки, которые имеют a) , но не b) . В мире функционального программирования a) (функции) и b (функции как замыкания) в настоящее время являются нормой. Smalltalk долгое время имел a) ( блоки - анонимные функции), но затем некоторые диалекты Smalltalk добавили поддержку b) (блоки в виде замыканий).

Вы можете себе представить, что вы получаете немного другую модель программирования, если добавляете функции и замыкания в язык.

С прагматической точки зрения анонимная функция добавляет несколько кратких обозначений, в которых вы передаете или вызываете функции. Это может быть хорошо.

Замыкание (функция плюс привязка) позволяет, например, создать функцию, которая имеет доступ к некоторым переменным (например, к значению счетчика). Теперь вы можете сохранить эту функцию в объекте, получить к ней доступ и вызвать ее. Контекст для объекта функции теперь представляет собой не только объекты, к которым он имеет доступ, но также и переменные, к которым он имеет доступ через привязки. Это также полезно, но вы можете видеть, что привязка переменных к доступу к объектным переменным теперь является проблемой: когда должна быть переменная лексическая (к которой можно получить доступ в замыкании) и когда это должно быть переменная какого-либо объекта ( слот ). Когда что-то должно быть закрытием или объектом? Вы можете использовать оба одинаковых способа. Обычное упражнение по программированию для студентов, изучающих Scheme (диалект Lisp), - это написание простой объектной системы с использованием замыканий.

В результате получается более сложный язык программирования и более сложная модель времени выполнения. Слишком сложно?

41 голосов
/ 08 сентября 2008

Они не делают объектно-ориентированный язык лучше. Они делают практические языки более практичными.

Если вы атакуете проблему с помощью молотка OO - представляйте все как взаимодействия между объектами - тогда закрытие не имеет смысла. На языке ОО, основанном на классе, затворы - это задворки, заполненные дымом, где все делается, но никто не говорит об этом потом. Концептуально это отвратительно.

На практике это очень удобно. Я на самом деле не хочу определять новый тип объекта для хранения контекста, устанавливать для него метод «делать вещи», создавать его экземпляр и заполнять контекст ... я просто хочу сказать компилятору: «посмотрите, посмотрите у меня есть доступ прямо сейчас? Это тот контекст, который я хочу, и вот код, для которого я хочу его использовать - держитесь за это для меня, пока он мне не понадобится.

Фантастические вещи.

36 голосов
/ 08 сентября 2008

Наиболее очевидной вещью будет псевдо-замена для всех тех классов, которые имеют только один метод, называемый run () или actionPerformed () или что-то в этом роде. Таким образом, вместо создания потока с внедренным Runnable, вы бы использовали вместо этого замыкание. Не более мощный, чем у нас сейчас, но гораздо более удобный и лаконичный.

Так нам нужны замыкания? Нет. Им было бы приятно иметь? Конечно, до тех пор, пока они не чувствуют себя разбитыми, как я боюсь, что они будут.

18 голосов
/ 08 сентября 2008

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

12 голосов
/ 15 сентября 2008

Есть несколько очень полезных «функций высшего порядка», которые могут выполнять операции со списками, используя замыкания. Функции высшего порядка - это функции, имеющие в качестве параметров «функциональные объекты».

например. это очень распространенная операция для применения некоторого преобразования к каждому элементу в списке. Эта функция более высокого порядка обычно называется «map» или «collect». (См. *. Оператор распространения Groovy ).

Например, чтобы выровнять каждый элемент в списке без замыканий, вы, вероятно, напишите:

List<Integer> squareInts(List<Integer> is){
   List<Integer> result = new ArrayList<Integer>(is.size());
   for (Integer i:is)
      result.add(i*i);
   return result;
}

Используя замыкания, карту и предложенный синтаксис, вы можете написать это так:

is.map({Integer i => i*i})

(Здесь возможна проблема с производительностью в отношении упаковки примитивных типов.)

Как объяснил Pop Catalin, есть еще одна функция более высокого порядка, называемая «select» или «filter»: ее можно использовать для получения всех элементов в списке, соответствующих какому-либо условию. Например:

Вместо:

void onlyStringsWithMoreThan4Chars(List<String> strings){
   List<String> result = new ArrayList<String>(str.size()); // should be enough
   for (String str:strings)
      if (str.length() > 4) result.add(str);
   return result;
}

Вместо этого вы могли бы написать что-то вроде

strings.select({String str => str.length() > 4});

используя предложение.

Вы можете взглянуть на синтаксис Groovy, который является расширением языка Java для поддержки замыканий прямо сейчас. См. Главу о коллекциях Groovy: руководство пользователя , где приведены дополнительные примеры действий с замыканиями.

Примечание:

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

Runnable createStringPrintingRunnable(final String str){
    return new Runnable(){
       public void run(){
          System.out.println(str); // this accesses a variable from an outer scope
       }
    };
}
8 голосов
/ 10 сентября 2008

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

Я не специалист по Java, но я использую C # 3.5, и замыкания являются одной из моих любимых функций языка, например, возьмем в качестве примера следующее утверждение:

// Example #1 with closures
public IList<Customer> GetFilteredCustomerList(string filter) {
    //Here a closure is created around the filter parameter
    return Customers.Where( c => c.Name.Contains(filter)).ToList();
}

теперь возьмите эквивалентный пример, который не использует замыкания

//Example #2 without closures, using just basic OO techniques
public IList<Customer> GetFilteredCustomerList(string filter) {
    return new Customers.Where( new CustomerNameFiltrator(filter));
}
... 
public class CustomerNameFiltrator : IFilter<Customer> {
    private string _filter;
    public  CustomerNameFiltrator(string filter) {
         _filter = filter;
    }
    public bool Filter(Customer customer) {
        return customer.Name.Contains( _filter);
    }
}

Я знаю, что это C #, а не Java, но идея та же, замыкания полезны для краткости и делают код короче и более читабельным. За кулисами замыкания C # 3.5 делают то, что выглядит очень похоже на пример # 2, что означает, что компилятор создает закрытый класс за кулисами и передает ему параметр 'filter'.

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

8 голосов
/ 04 апреля 2009

В последнее время я много читал о следующем выпуске Java, возможно, с поддержкой замыканий. Мне кажется, что я достаточно твердо понимаю, что такое замыкания, но я не могу придумать убедительного примера того, как они могли бы сделать объектно-ориентированный язык «лучше».

Ну, большинство людей, которые используют термин «замыкание», на самом деле имеют в виду «функциональный объект», и в этом смысле функциональные объекты позволяют писать более простой код в определенных обстоятельствах, например, когда вам нужны пользовательские компараторы в функции сортировки.

Например, в Python:

def reversecmp(x, y):
   return y - x

a = [4, 2, 5, 9, 11]
a.sort(cmp=reversecmp)

Это сортирует список a в обратном порядке, передавая пользовательское сравнение functoin reversecmp. Добавление лямбда-оператора делает вещи еще более компактными:

a = [4, 2, 5, 9, 11]
a.sort(cmp=lambda x, y : y - x)

В Java нет функциональных объектов, поэтому для их моделирования используются "классы функторов". В Java вы выполняете эквивалентную операцию, реализуя пользовательскую версию класса Comparator и передавая ее в функцию сортировки:

class ReverseComparator implements Comparator {
   public compare(Object x, Object y) {
      return (Integer) y - (Integer) x;
   }

...

List<Integer> a = Arrays.asList(4, 2, 5, 9, 11);
Collections.sort(a, new ReverseComparator());

Как видите, он дает тот же эффект, что и замыкания, но более неуклюж и более многословен. Тем не менее, добавление анонимных внутренних классов устраняет большую часть боли:

List<Integer> a = Arrays.asList(4, 2, 5, 9, 11);
Comparator reverse = new Comparator() {
   public Compare(Object x, Object y) {
       return (Integer) y - (Integer) x;
   }
}
Collections.sort(a, reverse);

Так что я бы сказал, что сочетание классов функторов + анонимных внутренних классов в Java достаточно, чтобы компенсировать отсутствие истинных функциональных объектов, делая их добавление ненужным.

5 голосов
/ 08 сентября 2008

Java имеет замыкания с 1.1, только очень громоздким и ограниченным образом.

Они часто полезны везде, где у вас есть обратный вызов какого-либо описания. Распространенным случаем является абстрагирование потока управления, оставляя интересный код для вызова алгоритма с замыканием, которое не имеет потока внешнего управления.

Тривиальный пример для каждого (хотя в Java 1.5 это уже есть). Хотя вы можете реализовать метод forEach в Java в его нынешнем виде, он слишком многословен, чтобы быть полезным.

Примером, который уже имеет смысл с существующей Java, является реализация идиомы «выполнения вокруг», посредством которой получение и освобождение ресурса абстрагируется. Например, открытие и закрытие файла может быть выполнено с помощью try / finally, при этом клиентскому коду не нужно правильно понимать детали.

3 голосов
/ 13 мая 2012

Теперь, когда JDK8 готовится к выпуску, появилась дополнительная информация, которая может обогатить ответы на этот вопрос.

Брия Гетц, языковой архитектор в Oracle, опубликовал серию статей (пока еще черновиков) о текущем состоянии лямбда-выражений в Java. Он также охватывает замыкания, так как они планируют выпустить их в предстоящем JDK, который должен быть завершен около января 2013 года и должен быть выпущен около середины 2013 года.

  • Лямбда-штат : на первой или двух страницах этой статьи предпринята попытка ответить на поставленный здесь вопрос. Хотя я все еще нашел это коротким в аргументах, но он полон примеров.
  • The State of Lambda - Библиотечное издание : это также очень интересно, поскольку охватывает такие преимущества, как ленивая оценка и параллелизм.
  • Перевод лямбда-выражений : который в основном объясняет процесс отладки, выполняемый компилятором Java.
3 голосов
/ 23 июня 2011

Я много думал о теме этого очень интересного вопроса в последние несколько дней. Прежде всего, если я правильно понял, Java уже имеет некоторые базовые понятия о замыканиях (определенные через анонимные классы), но новая функция будет представлена ​​поддержка замыканий на основе анонимных функций.

Это расширение определенно сделает язык более выразительным, но я не уверен если это действительно соответствует остальной части языка. Java была разработана как объектно-ориентированный язык без поддержки функционального программирования: будет ли легко понять новую семантику? В Java 6 даже нет функций, будут ли в Java 7 анонимные функции, но нет «нормальных» функций?

У меня сложилось впечатление, что в качестве новых стилей программирования или парадигм, таких как функционал программирование становится все более популярным, каждый хочет использовать их в своих любимый язык ООП. Это понятно: хочется продолжать пользоваться язык, с которым они знакомы при освоении новых функций. Но так язык может стать действительно сложным и потерять последовательность.

Так что я сейчас придерживаюсь мнения о том, чтобы придерживаться Java 6 для ООП (надеюсь, Java 6 все еще будет на некоторое время) и, в случае, если я действительно заинтересован в OOP + FP, взглянуть на какой-то другой язык, такой как Scala (Scala был определен как парадигма с самого начала и может быть хорошо интегрирована с Java), а не переключение в Java 7.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...