суббота, 24 апреля 2010 г.

[prog.flame] Попрограммировал на Java, делюсь впечатлениями. Часть III. Какую бы Java хотелось иметь (максимальный вариант).

Продолжение заметок о впечатлениях после программирования на Java. Предыдущие части здесь:

Часть I
Часть II

В этот раз я расскажу о том, что хотелось бы иметь в языке 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++. Наверное :)

Отправить комментарий