Статьи

Лучшие 10 особенностей языка Цейлона, которые я хотел бы иметь на Java

Что делать, когда Hibernate «закончен» и функционирует, и ему нужны новые задачи? Правильно. Создается новый язык JVM под названием Ceylon .

12 ноября 2013 года, наконец, был выпущен Ceylon 1.0.0, и мы поздравляем всю команду в Red Hat с ее достижениями в том, что выглядит как очень многообещающий новый язык JVM. Хотя Цейлону будет нелегко конкурировать со Scala , есть много очень интересных особенностей, которые отличают его.

На самом деле, этот язык имеет так много интересных функций, что будет сложно написать в блоге 10 самых интересных. Какие из них выбрать? В Google Plus у меня был короткий разговор с Гэвином Кингом, который также принес нам Hibernate , Россом Тэйтом, который также связан с Kotlin JetBrains , и Лукасом Ритцем, который был аспирантом и коммиттером в Scala EPFL, а теперь работает в Google Dart. , Я хотел, чтобы эти языковые Uberdesigners помогли мне найти 10 самых захватывающих языковых функций, которые у них есть, а у нас, разработчиков Java, нет. Сейчас у меня 20 интересных. Я непременно напишу следующий пост к этому.

Я заметил, что Гэвин Кинг и другие ребята были очень восторженными и знающими. У меня уже было такое впечатление, когда я впервые услышал о Цейлоне от Стефана Эпардо в JUGS в Берне, Швейцария, в феврале 2013 года , еще одного страстного инженера RedHat ( см. Слайды его презентации здесь ).

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

1. Модули

В Java Jigsaw откладывался примерно 34 раза, и мы только сейчас приближаемся к Java 8 GA! Да, у нас есть OSGi и Maven, и оба они очень хорошо работают для управления зависимостями во время выполнения (OSGi) или во время компиляции (Maven). Но сравните эту черную магическую конфигурацию Maven / OSGi, используя Apache Felix

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
35
36
<plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
  <version>2.1.0</version>
  <extensions>true</extensions>
  <executions>
    <execution>
      <id>bundle-manifest</id>
      <phase>process-classes</phase>
      <goals>
        <goal>manifest</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <supportedProjectTypes>
      <supportedProjectType>
        jar
      </supportedProjectType>
    </supportedProjectTypes>
    <instructions>
      <Bundle-SymbolicName>
        org.jooq
      </Bundle-SymbolicName>
      <Export-Package>*</Export-Package>
      <Import-Package>
        javax.persistence;resolution:=optional,
        org.apache.log4j;resolution:=optional,
        *
      </Import-Package>
      <_versionpolicy>
        [$(version;==;$(@)),$(version;+;$(@)))
      </_versionpolicy>
    </instructions>
  </configuration>
</plugin>

… с этим на Цейлоне:

1
2
3
4
5
6
7
"The second best ever ORM solution!"
module org.hibernate "3.0.0.beta" {
    import ceylon.collection "1.0.0";
    import java.base "7";
    shared import java.jdbc "7";
}

Наконец, все можно контролировать на уровне jar, включая видимость пакетов. Всего несколько строк кода. Пожалуйста, Java, интегрируйте поддержку мощных модулей Ceylon.

Стоит отметить, что Fantom — это другой язык с поддержкой встроенных модулей. См. Выступление JodaTime Стивена Колебурна на Devoxx 2011: «Светлые годы Fantom впереди Скалы?» , Стивен также принес нам ElSql, новый внешний SQL DSL для шаблонов Java.

2. Последовательности

Это первый раз, когда я вижу такого рода первоклассную поддержку последовательностей на типизированном языке. Цейлон не только поставляет все виды коллекционных литералов, но и знает типы для этих конструкций. Конкретно, вы можете объявить Iterable как таковой:

1
{String+} words = { "hello", "world" };

Обратите внимание на обозначение литерала. Он имеет тип {String+} , что означает, что он содержит хотя бы один элемент. Тип совместим по присваиванию с {String*} , который представляет возможно пустую последовательность. Очень интересно.

Это продолжается поддержкой литералов массива как таковых:

1
2
3
String[] operators = [ "+", "-", "*", "/" ];
String? plus = operators[0];
String[] multiplicative = operators[2..3];

… или кортеж литералов:

1
[Float,Float,String] point = [0.0, 0.0, "origin"];

Обратите внимание также на литерал диапазона 2..3 который позволяет извлекать 2..3 из исходного массива. Так много добра в последовательности на Цейлоне!

Заметьте также знак вопроса в String? , который является способом объявления Цейлона …

3. Обнуляемые типы

В то время как Scala знает тип Option, а Haskell знает тип Maybe, а Java 8 пытается конкурировать, добавляя новый неисполнимый тип Optional , в Ceylon очень простое представление о том, что можно обнулять. Если за типом стоит вопросительный знак, он может быть пустым. Иначе, это не нуль. Всегда.

Чтобы преобразовать обнуляемый тип в ненулевой тип, вы должны явно проверить:

01
02
03
04
05
06
07
08
09
10
11
void hello() {
    String? name = process.arguments.first;
    String greeting;
    if (exists name) {
        greeting = "Hello, ``name``!";
    }
    else {
        greeting = "Hello, World!";
    }
    print(greeting);
}

Обратите внимание на exists оператор. Он определяет новую область видимости, в пределах которой переменная name как известно, не равна нулю, т.е. она повышается из String? в String . По словам Лукаса Ритца, это продвижение типа в локальной области обычно называют типизацией, чувствительной к потоку, что уже наблюдалось на языке вей.

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

1
String greeting = "Hello, " + (name else "World");

Предложение else действует как функция SQL COALESCE() и даже может быть связано. Узнайте больше о злобной доброте Цейлона .

4. Параметры по умолчанию

О боже, как бы мне хотелось, чтобы это было на Яве. Каждый раз, когда мы перегружаем методы, мы думаем, почему бы просто не поддерживать параметры по умолчанию, такие как PL / SQL, например?

1
2
3
void hello(String name="World") {
    print("Hello, ``name``!");
}

Я не могу придумать ни одной веской причины, по которой языки не имели бы именованных и дефолтных параметров, таких как PL / SQL:

01
02
03
04
05
06
07
08
09
10
11
12
-- One of the parameters is optional
CREATE PROCEDURE MY_PROCEDURE (
  P1 IN NUMBER,
  P2 IN VARCHAR2 := 'ABC',
  P3 IN VARCHAR2
);
 
-- Calling the procedure
MY_PROCEDURE(
  P1 => 1,
  P3 => 'XYZ'
);

Так что это один из способов обойти перегрузку метода в большинстве распространенных случаев. Перегрузка методов все еще утомительна, когда мы хотим иметь дело с альтернативными, несовместимыми типами. Но не на Цейлоне, как Цейлон знает …

5. Союз типов

ОК, это немного эзотерично. Создатели Ceylon действительно очень хотели избавиться от перегрузки методов, отчасти потому, что Ceylon также компилирует в JavaScript , а JavaScript не знает перегрузки функций. На самом деле невозможно перегрузить методы на Цейлоне вообще. Однако, чтобы иметь возможность взаимодействовать с Java, необходимо ввести типы объединения. Тип объединения String|Integer может быть либо String, либо Integer. Там метод перегрузки прямо здесь!

1
2
3
4
5
void printType(String|Integer|Float val) { ... }
 
printType("hello");
printType(69);
printType(-1.0);

Для того, чтобы «распутать» тип объединения, вы можете снова воспользоваться чувствительной к потоку типизацией для параметра val , выполнив проверки типов, аналогичные Java instanceof

1
2
3
4
5
6
void printType(String|Integer|Float val) {
    switch (val)
    case (is String) { print("String: ``val``"); }
    case (is Integer) { print("Integer: ``val``"); }
    case (is Float) { print("Float: ``val``"); }
}

В этой области, например, известно, что компилятор val имеет тип String . Это позволяет использовать сумасшедшие вещи, такие как перечислимые типы, где типом может быть то или иное, одновременно:

1
2
3
4
abstract class Point()
        of Polar | Cartesian {
    // ...
}

Обратите внимание, что это сильно отличается от множественного наследования, где такая Point будет как Polar и Cartesian . Но это не все. Цейлон также имеет …

6. Типы пересечений

Теперь, как вы уже могли догадаться, это полная противоположность объединенному типу, и это на самом деле также поддерживается обобщениями Java. На Java вы можете написать:

1
class X<E extends Serializable & Comparable<E>> {}

В приведенном выше примере X принимает только параметры типа, которые могут быть как Serializable и Comparable . Это гораздо более безумно на Цейлоне, где вы можете назначать значения локально объявленному типу пересечения. И это еще не все! В нашем чате Гэвин указал мне на эту невероятную языковую особенность, где типы объединения / пересечения могут взаимодействовать с чувствительной к потоку типизацией, чтобы сформировать следующее ( из-за Цейлона 1.2 ):

1
2
3
4
5
6
7
value x = X();
//x has type X
if (something) {
    x = Y();
    //x has type Y
}
//x has type X|Y

Имеет смысл, верно? Поэтому я спросил его, смогу ли я снова пересечь этот тип с Z и Гэвин сказал: да! Можно сделать следующее:

01
02
03
04
05
06
07
08
09
10
value x = X();
//x has type X
if (something) {
    x = Y();
    //x has type Y
}
//x has type X|Y
if (is Z x) {
    //x has type <X|Y>&Z
}

И это продолжается, потому что пересечения типов также взаимодействуют с обобщениями очень интересным способом. При определенных обстоятельствах X<A>&X<B> может совпадать с X<A&B> . Другими словами, пересечения (и объединения) являются дистрибутивными с обобщениями, точно так же, как сложения с умножениями (в неформальном понимании «просто как»). Если вы хотите в этом разобраться со спецификацией языка, см. §3.7.2 Наследование основного экземпляра .

Теперь типы объединения и пересечения могут стать довольно неприятными и сложными для повторного использования. Вот почему Цейлон имеет …

7. Введите псевдонимы

Есть ли другой язык программирования, который когда-либо думал об этой удивительной функции ?? Это очень полезно, даже если вы не поддерживаете типы объединения и / или пересечения. Подумайте о дженериках Java. С появлением дженериков люди начали писать такие вещи, как:

1
Map<String, List<Map<Integer, String>>> map = // ...

Можно сказать две вещи:

  • Обобщения чрезвычайно полезны для библиотек Java
  • Дженерики становятся чрезвычайно многословными при выполнении вышеуказанного

Вот где в игру вступают псевдонимы типа. Проверьте этот пример:

1
interface People => Set<Person>;

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

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

1
2
3
People?      p1 = null;
Set<Person>? p2 = p1;
People?      p3 = p2;

Таким образом, как подсказывает термин «псевдоним», вы не создаете новый тип. Вы просто даете сложному типу более простое имя. Но даже лучше, чем псевдоним типа …

8. Тип вывода

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

1
2
3
4
5
6
7
interface Foo {}
interface Bar {}
object foobar satisfies Foo&Bar {}
//inferred type Basic&Foo&Bar
value fb = foobar;
//inferred type {Basic&Foo&Bar+}
value fbs = { foobar, foobar };

Итак, в этом примере показано множество объединенных функций, включая ограничения типов, типы последовательностей, типы объединения. При такой богатой системе типов очень важно поддерживать этот уровень вывода типа, когда ключевое слово value указывает, что вы не хотите (или не можете) явно объявлять тип. Это я бы очень хотел увидеть в Java 9!

Узнайте больше о потрясающих возможностях вывода типов на Цейлоне.

9. Декларация сайта отклонений

Теперь эту функцию может быть немного сложнее понять, так как дженерики Java уже довольно сложны для понимания. Недавно я прочитал очень интересную статью Росса Тейта, Алана Леунга и Сорина Лернера о трудностях, с которыми сталкиваются дженерики Java с помощью подстановочных знаков: приручение подстановочных знаков в системе типов Java . Обобщения являются все еще очень активной темой исследования, и ни исследователи, ни разработчики языка не полностью согласны с тем, действительно ли дисперсия сайта использования (как в Java) или декларация сайта (как в C #, Scala или Ceylon) действительно лучше для программистов основного направления. Более древние языки, говорящие о дисперсии, это Eiffel и OCaml .

Microsoft ввела декларацию отклонений сайта в C #. Я приведу пример из Википедии , который очень легко понять. В C # интерфейс IEnumerator имеет ковариантный параметр универсального типа:

1
2
3
4
5
interface IEnumerator<out T>
{
    T Current { get; }
    bool MoveNext();
}

Это просто означает, что будет работать следующее:

1
2
IEnumerator<Cat> cats = ...
IEnumerator<Animal> animals = cats;

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

1
2
Iterator<Cat> cats = ...
Iterator<? extends Animal> animals = cats;

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

01
02
03
04
05
06
07
08
09
10
// Given this interface:
public interface X<E> {
    E get();
    E set(E e);
}
 
// This does not compile:
public void foo(X<?> x) {
    x.set(x.get());
}

Как видно из языкового тура по Цейлону, дженерики Цейлона поддерживают дисперсию объявлений на сайте , как в C # и Scala. Будет интересно посмотреть, как эти вещи будут развиваться, поскольку оба типа поддержки отклонений имеют свои плюсы и минусы, в то же время Росс Тейт выступает за смешанную вариацию сайтов , которая действительно станет отличным дополнением для языка Java!

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

10. Функции и методы

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

01
02
03
04
05
06
07
08
09
10
Integer f1() => 1;
class C() {
    shared Integer f2() {
        Integer f3() => 2;
        return f3();
    }
}
 
print(f1());
print(C().f2());

В приведенном выше примере

  • f1() — функция уровня пакета (очень похожая на «глобальную» статическую функцию в Java)
  • f2() — это обычный метод класса C
  • f3() является локальной функцией в методе f2()

Благодаря поддержке лямбда-выражений в Java 8 эти вещи становятся немного лучше, но разве не удивительно, что можно объявлять функции где угодно, с почти одинаковым синтаксисом?

Вывод: поиграй с Цейлоном

Это все на данный момент. Возможно, скоро мы опубликуем дополнительную статью о более эзотерических языковых особенностях Цейлона. В любом случае вы можете скачать этот интересный язык JVM бесплатно с первоклассной поддержкой IDE в Eclipse . Вы также можете посетить веб-сайт документации Ceylon и сделать так, чтобы их сайт компилировал код Ceylon в JavaScript для выполнения в вашем браузере.

Посетите сообщество и пообщайтесь с дизайнерами языков из RedHat и Serli , а когда закончите, поделитесь этой статьей в нашем блоге jOOQ и помогите JCP осознать, что этот замечательный язык имеет несколько очень интересных функций, которые можно добавить в Java 9 или 10 дорожных карт!