Продолжение заметок о впечатлениях после программирования на Java. Предыдущие части здесь:
В этот раз я расскажу о том, что хотелось бы иметь в языке Java, если бы его более-менее серьезно переработать (как язык, так и саму JVM).
Свой рассказ я разделил на две части. В первой части описываются то, что имеет более-менее осязаемые очертания. Тогда как во второй части речь пойдет о том, что хотелось бы видеть, но пока не понятно, как именно это должно выглядеть.
Итак, сначала о том, что я себе хорошо представляю.
1. Мне пришлось в своем проекте на Java перепаковывать биты в байты и обратно – после C++ без unsigned char-ов и unsigned int-ов было непривычно. Приходилось делать лишнюю работу, мелкую и нудную. Поэтому очень хотелось бы видеть в Java хотя бы unsigned byte. Чтобы можно было писать так:
class ByteTest { static public void main(String[] args) { ubyte v = 0x83; ubyte b = v >> 1; } } |
Насколько я понимаю, беззнаковые целые не поддерживаются на уровне JVM. Зря они так.
2. Очень бы не помешали туплы для того, чтобы из методов можно было возвращать несколько значений. Этого и в C++ нынешнем нет, но в C++ хотя бы есть возможность передавать аргументы по неконстантным ссылкам/указателям. Поэтому в C++ возврат нескольких int-ов из функции/метода гораздо проще, чем в Java. Так что, имхо, в Java туплы даже нужнее, чем в C++.
Очевидно, что туплы – это штука неоднозначная (как и автоматический вывод типов). Сегодня одному разработчику будет удобно возвращать из метода тупл. А через год другой разработчик будет гадать – такой же смысл имеет третий элемент возвращенного тупла. Поэтому использование туплов можно было бы ограничить: например, запретить их возврат из public-методов классов. Тогда бы и разработка на Java упростилась бы, и сопровождаемость кода не пострадала бы. Лично мне было бы приятнее писать так:
class Demo { public void doSomething() { int min; int max; (min, max) = findMinAndMax(); ... } private (int, int) findMinAndMax() { int min = someInternalData[0]; int max = someInternalData[0]; for( int item : someInternalData ) { if( item < min ) min = item; if( item > max ) max = item; } return (min, max); } ... } |
а не так, как сейчас:
class Demo { public void doSomething() { MinAndMax minMax = findMinAndMax(); ... } private (int, int) findMinAndMax() { MinAndMax result = new MinAndMax( someInternalData[0], someInternalData[0] ); for( int item : someInternalData ) { if( item < result.min ) result.min = item; if( item > result.max ) result.max = item; } return result; } ... } private class MinAndMax { int min; int max; MinAndMax(int min, int max) { this.min = min; this.max = max; } } |
3. В Java очень сильно не хватает константности объектов. В C++ я к этому привык. Бывало, что компилятор бил разработчика по рукам, предупреждая ошибки. Да и в связи с hype вокруг функционального программирования и многоядерных процессоров, языки без константности для объектов уже выглядят опасными.
За основу я бы взял вариант из языка D, но без хвостовой константности. Разделил бы все ссылки на три типа: обычные, readonly и immutable. Если есть обычная ссылка на объект, то у объекта можно вызывать любые методы. Если есть readonly-ссылка или immutable-ссылка, то можно вызывать только immutable-методы.
Соответственно, объекты в программе делятся на обычные (изменяемые) и immutable (изменить которые нельзя). Обычные объекты можно передавать по обычным или readonly ссылкам, но нельзя передавать по immutable-ссылке.
Смысл у readonly-ссылок такой же, как у const-ссылок и указателей в C++: мы не можем модифицировать объект, но эта модификация может быть разрешена для кого-то другого. Например:
class UserInfo { private String name; UserInfo(String name) { this.name = name; } immutable public String name() { return this.name; } public String setName(String name) { this.name = name; } } class UserObserver { readonly private UserInfo user; UserObserver(readonly UserInfo user) { this.user = user; } public void print() { System.out.println(user.name()); } } class Demo { static public void main(String[] args) { UserInfo ui = new UserInto("John Smith"); UserObserver observer = new UserObserver(ui); observer.print(); // => John Smith ui.setName("John Woo"); observer.print(); // => John Woo } } |
А вот смысл immutable в том, что компилятор гарантирует, что объект никогда не изменится. Если есть immutable ссылка на объект A, то компилятор разрешает вызывать только те методы объекта A, которые помечены как immutable. И компилятор следит за тем, чтобы внутри immutable-методов состояние объекта не изменялось.
Представим, что в примере выше в классе UserObserver хранилась бы не readonly, а immutable ссылка на UserInfo. В этом случае UserObserver был бы защищен от “внезапных” модификаций объекта UserInfo где-то в других местах программы.
Отсутствие хвостовой константности (т.е. когда все ссылки внутри immutable объекта автоматически становятся immutable ссылками) позволяет легко организовывать обмен сообщениями между нитями в программе. Например, пусть есть сообщение ChangeLogStreamNotify, которое рассылается сразу нескольким нитям:
class ChangeLogStreamNotify { public LogStream stream; ChangeLogStreamNotify(LogStream stream) { this.stream = stream; } } |
Сам объект сообщения поступает подписчикам в виде immutable объекта. Поэтому подписчики не смогут заменить в нем значение stream. Т.е. при доступе к экземпляру ChangeLogStreamNotify из разных потоков не нужно никакой синхронизации.
Однако, ссылка на LogStream внутри ChangeLogStreamNotify является обычной ссылкой. Поэтому для объекта LogStream можно вызывать любые методы:
class WorkThread { public void onChangeLogStreamNotify( immutable ChangeLogStreamNotify notify) { notify.stream.debug("log stream changed"); } } |
Ну а теперь перейдем к тем возможностям, которые я хотел бы видеть в Java, но пока не представляю себе, как бы они выглядели.
1. В Java не хватает хоть какого-то подобия нормального множественного наследования или же каких-то механизмов подмешивания общего кода в разные классы. Что-то типа механизма mixin-ов из Ruby или trait-ов из Scala. Хотя в Scala, имхо, trait-ы переусложнили. Нужно бы чего-нибудь попроще.
Например, был у меня интерфейс TlvCompoundItem с одним методом tlvFields.
public interface TlvCompoundItem extends TlvItem { TlvFieldInfo[] tlvFields(); } |
Этот интерфейс реализует базовый класс для PDU (элементов прикладного протокола):
abstract public class Pdu implements TlvCompoundItem { @Override abstract public TlvFieldInfo[] tlvFields(); ... } |
И затем каждый наследник Pdu должен определять у себя tlvFields одним и тем же способом:
public class SomeConcretePdu extends Pdu { ... @Override public TlvFieldInfo[] tlvFields() { return tlvFieldsDecription; } static private TlvFieldInfo[] tlvFieldsDescription = ...; } |
Т.е. в каждом наследнике Pdu есть статический атрибут с описанием TLV-полей, и всегда он имеет имя tlvFieldsDescription. И реализация метода tlvFields всегда возвращает этот атрибут.
Вот это дублирование одинакового кода tlvFields и не нравится. Можно было бы поступить как-то так (это только набросок):
mixin PduFieldsGetter requires(TlvFieldInfo[] fields) { public TlvFieldInfo[] tlvFields() { return fields; } } public class SomeConcretePdu extends Pdu uses PduFieldsGetter(tlvFieldsDescription) { ... // Здесь уже не нужно объявлять tlvFields(). static private TlvFieldInfo[] tlvFieldsDescription = ...; } |
2. Хотелось бы иметь в Java ссылки, которые гарантированно не могут быть нулевыми. Т.е., чтобы Java предлагала бы решение проблемы нулевых ссылок, как это сейчас делает Eiffel.
Только вот как это делать – это вопрос. Либо же в язык вводится понятие ненулевой ссылки (например, String! вместо String) и компилятор будет отслеживать действия с этой ссылкой (так попытались сделать в языке Nice). Либо же нужно добавлять в язык что-то вроде типа Option (с сопутствующими ему вариантами None и Some) и паттерн-матчинга, чтобы нужно было явно разделять ситуации с нулевыми и ненулевыми ссылками.
Я бы предпочел вариант без паттерн-матчинга и типа Option. Запись String! более привычна для императивного программиста. Да и гигатонны уже написанного Java кода в таком случае было бы гораздо проще переиспользовать.
Вот такие у меня мысли о том, как сделать из Java нормальный ;) язык. Если собрать эти предложения и то, о чем я писал раньше, то получится неплохой, прагматичный язык. На который я бы лично перешел бы с C++. Наверное :)
12 комментариев:
А так ли необходимы различные const модификаторы?
Не приведет ли их введение к необоснованному усложнению языка?
Как часто в твоей практике сишный конст не мог решить проблему?
По поводу нулевых значений.
У меня практически отсутствует опыт работы с явой поэтому интересно -- нулевые ссылки, это реальная проблема, для которой стоит городить костыли по типу Eiffel?
Например в sql с null вполне удобно работать и в с++ мне его не хватает.
такие вот вопросы :)
>А так ли необходимы различные const модификаторы?
Да. Механизм const в C++ очень хорошая штука. Это часть того, что меня в C++ до сих пор удерживает. Но этот механизм половинчатый. Он дает только readonly view (не знаю, как это лучше по русски сказать). Дополнить бы его и настоящей иммутабельностью -- вот это было бы здорово.
>Не приведет ли их введение к необоснованному усложнению языка?
Сам язык, наверняка, усложнится. Но использовать его будет проще.
>Как часто в твоей практике сишный конст не мог решить проблему?
Да есть целый комплекс проблем (причем в языках с GC, как в Java). Например, в конструктор какого-нибудь объекта A передается ссылка на второй объект B. Объект A должен сохранить у себя B. Как он может это сделать? Выполнить dup/clone чтобы получить автономную копию? Или же можно просто сохранить ссылку, зная, что объект не изменится. Сишный конст ничего на эту тему не говорит. А вот immutable был бы в тему.
>нулевые ссылки, это реальная проблема, для которой стоит городить костыли по типу Eiffel?
Ну в прямых руках не страшно :) Но даже я их часто забываю выпрямить. Так что лишние гарантии со стороны компилятора не помешают. В Java null-ссылки встречаются даже чаще, чем в C++ нулевые указатели. Поскольку в C++ есть ссылки, которые просто так нулевыми не сделать. А в Java все на ссылках. Поэтому там такой механизм даже важнее, чем в плюсах.
Евгений Охотников>Например, в конструктор какого-нибудь объекта A передается ссылка на второй объект B. Объект A должен сохранить у себя B. Как он может это сделать? Выполнить dup/clone чтобы получить автономную копию? Или же можно просто сохранить ссылку, зная, что объект не изменится. Сишный конст ничего на эту тему не говорит. А вот immutable был бы в тему.
а если immutable сделать шаблонным классом, это не решит проблему?
>а если immutable сделать шаблонным классом, это не решит проблему?
В С++ или в Java?
с++
Имхо, к C++ варианты с readonly и immutable ссылками не применимы. Поскольку в C++ нет сборки мусора. Поэтому в С++ как такового immutable объекта быть не может -- кто-то за ним должен следить. (Я сейчас не про const-объекты, которые компилятор вообще может в ROM определить).
Так что все мои слова нужно относить только к языкам с GC (вроде Java, C#, D и т.д.).
Про множественное наследование слышал, а что такое
mixin-ов из Ruby или trait-ов из Scala?
Эти понятия (mixin и trait) как-нибудь переводятся на русский? Где можно посмотреть примеры их использования?
2Quaker:
Про Ruby-овые mixin-ы можно прочитать в моей статье о Ruby на RSDN (http://www.rsdn.ru/article/ruby/ruby_edges.xml#ELZAE).
Про Scala-вские trait-ы я в первый раз читал так же на RSDN: http://rsdn.ru/article/philosophy/Scala.xml#ENQAE
Но, возможно, это описание уже устарело. Новое нужно искать где-нибудь на scala-lang.org (например, http://www.scala-lang.org/node/126)
С траитами в Scala, AFAIK, есть очень тонкий момент. Называется он, кажется, линеаризация. Это когда все, что класс к себе подмешал, выстраивается в линейный список и определяется кто кого скрывает. Вот эта штука мне показалась сложной и неоднозначной. Сейчас уже не помню, но вроде бы от порядка следования mixin-ов в описании класса само поведение класса могло изменяться.
Ты или например я и другие плюсисты на такой язык да перешли бы, но есть подозрения что многое явисты с него сбежали бы :)
2Rustam: насколько я знаю, многие java-исты сбегают на тот язык, за программирование на котором платят :) Если будут платить за такой, то перейдут и на него :))
Про immutable vs mutable/readonly всё не так просто.
Если immutable объект конструируется строго извне - то мы можем гарантировать его чистоту.
А если у него есть конструктор?
В рамках конструктора объект может (и, наверно, должен) изменять себя - дописывать значения членов-данных, например. Иначе смысл конструктора теряется.
Но в этот отрезок времени объект mutable. И значит, он может заначить ссылку на самого себя.
Тут-то мы и отхватим проблем с алиасингом (двумя разнотипными ссылками на один объект).
>В рамках конструктора объект может (и, наверно, должен) изменять себя - дописывать значения членов-данных, например. Иначе смысл конструктора теряется.
Думаю, что в конструкторе immutable объекта можно разрешать только списки инициализаторов. Если объекту этого недостаточно, то на помощь придет идиома PImpl, где содержимое объекта будет конструироваться вспомогательной функцией-фабрикой.
Отправить комментарий