Статьи

Альтернативы синтаксиса Java лямбда

Обсуждение списка рассылки lambda-dev началось с вопроса о том, как должен выглядеть синтаксис языка Java для литералов lambdas / function. Давайте посмотрим на немного нетривиальный пример и попытаемся выявить проблемы.

У людей Perl есть хороший пример чего-то, что использует ссылки на функции несколько функциональным способом — они называют это преобразованием Шварца (но я полагаю, что это изначально трюк Лиспа, иногда называемый decorate-sort-undecorate). Поскольку здесь есть только мы, JVM-цыплята, я переписал это на Clojure (это один из моих примеров в главе 9 книги).

Вот фрагмент кода Clojure, который определяет функцию для выполнения преобразования Шварца. По сути, он обеспечивает очень простой способ сортировки списка на основе вспомогательной функции (называемой «ключевой функцией»), предоставляемой вызывающей стороной.

1
2
3
4
5
(defn schwarz [x f]
  (map #(nth %1 0)
       (sort-by #(nth %1 1)
                (map #(let [w %1]
                     (list w (f w)) ) x))))

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

Как это может выглядеть в различных предложенных вариантах синтаксиса Java? Давайте кратко рассмотрим каждый из них (обратите внимание, что, поскольку система типов Java гораздо более статична, многие наши объявления типов более чем многословны):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Strawman, with round brackets for single-expression lambdas
public List<T> schwarz(List<T> x, Function<T, Pair<T,V extends Comparable<T>>> f) {
return map(#(T w)(makelist(w, f.apply(w))), x)
       .sort(#(Pair<T, V extends Comparable<T>> l)(l.get(1)))
       .map(#(Pair<T, V extends Comparable<T>> l)(l.get(0)));
}
 
// Strawman, with braces for all lambdas
public List<T> schwarz(List<T> x, Function<T, Pair<T,V extends Comparable<T>>> f) {
return map(#(T w){makelist(w, f.apply(w))}, x)
       .sort(#(Pair<T, V extends Comparable<T>> l){l.get(1)})
       .map(#(Pair<T, V extends Comparable<T>> l){l.get(0)});
}
 
// BGGA
public List<T> schwarz(List<T> x, Function<T, Pair<T,V>> f) {
return map({T w -> makelist(w, f.apply(w))}, x)
       .sort({Pair<T, V extends Comparable<T>> l -> l.get(1)})
       .map({Pair<T, V extends Comparable<T>> l -> l.get(0)});
}
 
// SotL
public List<T> schwarz(List<T> x, Function<T, Pair<T,V>> f) {
return map(#{T w -> makelist(w, f.apply(w))}, x)
       .sort(#{Pair<T, V extends Comparable<T>> l -> l.get(1)})
       .map(#{Pair<T, V extends Comparable<T>> l -> l.get(0)});
}
 
// Redmond
public List<T> schwarz(List<T> x, Function<T, Pair<T,V extends Comparable<T>>> f) {
return map((T w) -> {makelist(w, f.apply(w))}, x)
       .sort((Pair<T,V extends Comparable<T>> l) -> {l.get(1)})
       .map((Pair<T, V extends Comparable<T>> l) -> {l.get(0)});
}

Как их оценить? Мои критерии:

  1. Необходимо начинать с видимой идентификационной метки, чтобы лямбды выделялись из окружающего кода. # Это удобный символ для этого.
  2. Необходимо использовать синтаксис-разделитель {}. Замыкания являются своего рода блоком, поэтому они должны быть похожи на блоки в коде.
  3. Необходимо, чтобы все было в одном фрагменте, поэтому синтаксис имеет визуальную согласованность, и лямбда выглядит как единое целое
  4. Желательно иметь специальную краткую форму для функциональных литералов, которые не принимают параметров (нулевые лямбды).

Исходя из этих критериев, Редмонд — худший из возможных вариантов для меня — и мой опыт написания Scala для этой книги подтверждает это — я обнаружил, что функциональные литералы Scala гораздо сложнее использовать без проблем, чем в других языках. BGGA немного лучше, но мне не нравится отсутствие простой идентификационной метки, которая говорит мне: «Привет! Я лямбда ».

Это сводит его к выбору между SotL и Strawman с постоянной скобкой. Выбор этих двух несколько произвольный. Strawman-Always-Brace выглядит, на мой взгляд, как настоящее объявление метода Java, но с «волшебным именем» # — тогда как SotL — это действительно новый синтаксис, но он чувствует себя ближе к стилям Redmond и BGGA — поэтому вполне может быть приемлемым компромиссом для разработчиков, которым удобны эти формы.

Объединяя все это, я предпочитаю:

  1. SotLhttp: //www.blogger.com/img/blank.gif
  2. Strawman-всегда распорка
  3. BGGA
  4. Strawman однозначного выражения круглое
  5. Redmond

Пожалуйста, используйте комментарии (ниже или в оригинальном источнике ), чтобы рассказать нам, что вы думаете об этой проблеме. Конечно, это не будет в Java 7 — но еще не слишком рано думать о Java 8 и будущем.

Ссылка: Альтернативы синтаксиса лямбда от наших партнеров JCG в блоге Java 7 для разработчиков .

Статьи по Теме :