Статьи

Цейлон мог быть просто единственным языком (JVM), который получил нулевое значение

Это снова мы. ЭТА ТЕМА .

Но держись. Обсуждаемый здесь подход (и на языке Цейлона) — это не то, что вы видите каждый день. В то же время это очень хитро.

Нули запекаются на языке

… или так может показаться. Действительно, в Цейлоне, как и в Kotlin (и, возможно, во многих других языках), есть специальный тип «аннотация», который можно добавить к любому ссылочному типу, чтобы сделать его обнуляемым. Например:

1
2
3
String firstName = "Homer";
String? middleName = "J";
String lastName = "Simpson";

В приведенном выше примере и firstName и lastName являются обязательными значениями, которые никогда не могут быть null , тогда как middleName является необязательным. Большинство языков, которые поддерживают вышеупомянутое, затем поставляются со специальными операторами для доступа к необязательному значению, например .? на Цейлоне, а также в Котлине .

1
2
3
4
5
// Another optional value:
Integer? length = middleName.?length;
 
// A non-optional value:
Integer length = middleName.?length else 0;

Итак, что же такого в Цейлоне, который работает так гладко?

То, что Цейлон очень правильно понял, это то, что все вышеперечисленное — это просто синтаксический сахар:

  • Легко использовать
  • Карты хорошо для нашего мышления, где null все еще вещь
  • Может взаимодействовать с Java
  • Не вводит когнитивное трение

Для нас, Java, мы все еще можем притворяться, что null — это хорошо, трудно избежать ( как мы заявляли ранее в этом блоге ). Но что на самом деле является null ? Это отсутствующая ценность? Неизвестное значение? Неинициализированное значение?

У Java есть только одна null вещь, и она (ab-) используется для всех предыдущих вещей, и более того, когда в теории это только действительно неинициализированная ценность, ничего более. С другой стороны, при работе с JDBC (и, следовательно, SQL) это неявно означает неизвестное значение ( со всеми связанными предостережениями ).

В Цейлоне, однако, Null — это особый тип , похожий на Void в Java. Единственное значение, которое может быть назначено типу Null это null :

1
2
3
4
5
// Ceylon
Null x = null;
 
// Java
Void x = null;

Но большая разница в том, что null нельзя назначать никаким другим типам! Подождите. Разве мы не можем присвоить null для String? …? Конечно, на Цейлоне возможно следующее:

1
String? x = null;

Но почему это возможно? Потому что String? это просто синтаксический сахар для String|Null , типа объединения , то есть типа, который является либо типом String либо типом Null .

Да, что такое профсоюзные типы?

Давайте посмотрим на это более внимательно. Когда в jOOQ API вы хотите работать с функциями и выражениями SQL, всегда есть большой набор перегрузок, которые предоставляют вам стандартную версию и удобную версию, где вы можете передавать переменную связывания. Возьмите оператор равенства, например:

1
2
3
4
interface Field<T> {
    Condition eq(Field<T> field);
    Condition eq(T value);
}

Перечисленные выше перегрузки позволяют вам писать что-то вроде следующего, не думая о различии между выражением SQL и переменной связывания Java (которое в конечном счете также является выражением SQL):

1
2
3
4
5
// Comparing a column with bind variable
.where(BOOK.ID.eq(1))
 
// Comparing a column with another column expression
.and(BOOK.AUTHOR_ID.eq(AUTHOR.ID))

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

1
2
3
4
5
6
interface Field<T> {
    Condition eq(Field<T> field);
    Condition eq(T value);
    Condition eq(Select<? extends Record1<T>> query);
    Condition eq(QuantifiedSelect<? extends Record1<T>> query);
}

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

1
2
3
4
5
6
7
8
interface Field<T> {
    Condition eq(
        Field<T>
      | T
      | Select<? extends Record1<T>>
      | QuantifiedSelect<? extends Record1<T>> thingy
    );
}

Или даже

01
02
03
04
05
06
07
08
09
10
11
// This is called a type alias. Another awesome
// Ceylon language feature (pseudo syntax)
alias Thingy =>
    Field<T>
  | T
  | Select<? extends Record1<T>>
  | QuantifiedSelect<? extends Record1<T>>;
 
interface Field<T> {
    Condition eq(Thingy thingy);
}

В конце концов, так же определяется язык SQL. Черт, вот как любая нотация BNF определяет синтаксические элементы. Например:

01
02
03
04
05
06
07
08
09
10
11
<predicate> ::=
    <comparison predicate>
  | <between predicate>
  | <in predicate>
  | <like predicate>
  | <null predicate>
  | <quantified comparison predicate>
  | <exists predicate>
  | <unique predicate>
  | <match predicate>
  | <overlaps predicate>

Да, конечно, синтаксический элемент не совсем то же самое, что и тип, но интуитивное восприятие — то же самое.

О, и у Java тоже есть типы объединения!

Вкратце рассказав, группы экспертов по Java 7 добавили поддержку типов объединения в обработке исключений. Вы можете написать такие вещи, как:

1
2
3
4
5
6
try {
    ...
}
catch (IOException | SQLException e) {
    // e can be any of the above!
}

Вернуться к Цейлону и NULL

Цейлон получил Null право. Потому что исторически обнуляемый тип — это тип, который может быть «реальным» типом или «нулевым» значением. Мы хотим этого. Мы, Java разработчики, жаждем этого. Мы не можем жить без успокаивающего варианта такого рода по желанию.

Но отличительной особенностью этого подхода является то, что он расширяемый. Что, если мне действительно нужно провести различие между «неизвестным», «неинициализированным», «неопределенным», «42»? Я могу. Использование типов. Вот строка, которая может моделировать все вышеупомянутые «специальные значения»:

1
String|Unknown|Uninitialised|Undefined|FortyTwo

И если это слишком многословно, я просто присваиваю ему имя

1
2
interface TheStringToRuleThemAll
  => String|Unknown|Uninitialised|Undefined|FortyTwo;

Но это не может быть Null . Потому что я не хочу, чтобы это была та ценность, это все и ничего. Вы убеждены? Бьюсь об заклад, вы. Впредь:

Не верьте никаким языкам, которые притворяются, что монада Option (al) — достойный подход к моделированию нуля. Это не так.

— мне. Прямо сейчас

Почему? Позвольте мне проиллюстрировать. Синтаксический сахар в стиле Kotlin / Ceylon / Groovy с использованием оператора elvis (независимо от нулевой семантики поддержки):

1
String name = bob?.department?.head?.name

То же самое с Optional монадами:

1
2
3
4
Optional<String> name = bob
    .flatMap(Person::getDepartment)
    .map(Department::getHead)
    .flatMap(Person::getName);

И бедные, если вы смешаете карту () с flatMap () ТОЛЬКО ОДИН РАЗ !!

Некоторые люди утверждают:

Использование профсоюзных типов похоже на поездку на новом Ferrari с твоей свекровью на пассажирском сиденье.

от Эльвиры

Конечно. Но я утверждаю: хорошо, Цейлон. Будем надеяться, что мы получим объединение типов и в Java вне блоков catch!