Alex Polozov (skiminog) wrote,
Alex Polozov
skiminog

Category:
  • Music:

Here Comes Tiburon. Part I

Пришла мне наконец написать давно планируемый пост про один из самых знаменательных релизов Borland, CodeGear и Embarcadero. Про релиз, который, право слово, станет знаменательным. Про релиз, который приносит в нашу жизнь вагон возможностей, который ставит Делфи в один ряд с возможностями многих признанных языков и технологий... короче, харе пафоса =) Всё очень просто. Сегодня я хочу написать в блоге про Delphi & C++Builder 2009 aka Tiburon.

Согласно статье на eWeek, с 25 августа начинается приём заказов. Немногим позже продукт официально выходит в продажу.

Приношу свои извинения за то, что "пост" по факту представляет собою три поста: даже у ЖЖ, как выяснилось, есть ограничения на объём записи (да что вы говорите... :-D ). Каждый пост будет оформлен в виде списка возможностей с пунктами и подпунктами. А по каждому подпункту - краткое описание плюс кучка ссылок. Ссылок много, очень много, пост будет полон ими чуть более, чем наполовину. Практически все из них - на инглише, но это дело десятое, я полагаю. Интересующемуся человеку прочесть не составит труда, угу? Итак, я начинаю.

Оглавление:
 —> Part I: Юникод и параметризированные типы aka дженерики.
Part II: Анонимные методы, IDE и VCL, разнообразное прочее.
Part III: Нововведения в С++Builder`e.


I. Unicode.
Ну это как бы уже давно не новость, все, кто можно, давно слышали краем уха о поддержке Юникода в Делфи. Но всё-таки я распишу это дело до мелочей. Попутно, возможно, переведя кусочки с американских статеек.

  1. Итак, на полную поддержку Юникода переведено всё. Нет, вы не поняли: ВСЁ. VCL, RTL, среда, компилятор, контролы, библиотеки БД (dbExpress etc.), COM, Web-контролы... полностью по дефолту юникодное и полностью кастомизируется с точки зрения кодировок. Поясняю. Как известно, в Delphi 1 строковый тип по дефолту был ещё Турбо-Паскалевским ShortString. Начиная с Delphi 2 и до теперешнего момента string по дефолту стал Windows-Ansi-строкой:
    type
      string = AnsiString;
    То же касается PChar, который по факту означал PAnsiChar, и т.д. Была также COM-Unicode-строка - WideString, эквивалент для С++ BSTR, медленная и тяжёлая. И к тому же вдобавок ещё и не всеми поддерживаемая. Но теперь всё изменилось. И у нас новый дефолт:
    type
      string = UnicodeString;
      Char = WideChar;
      PChar = PWideChar;
    Что такое UnicodeString? Это новый тип. Использовать для full native Unicode support древний WideString ни у кого не было никакого желания, поэтому за работу взялись серьёзно. Более того, этим не ограничились. Давайте теперь внимательнее взглянем на AnsiString:
    type
      UTF8String = type AnsiString(CP_UTF8);
      RawByteString = type AnsiString($FFFF);

    Всё правильно. В круглых скобках отныне указывается кодовая страница (codepage) в международном стандарте: от $0000 до $FFFF. Так, к примеру, классическая кириллическая кодировка Windows - это $1251 (она же CP1251, Windows-1251, и т.д.). А CP_UTF8 в вышеприведённом коде, как легко догадаться, не более чем константа:
    const
      CP_UTF8 = $65001;

    Полный обзор кодовых страниц имеется... конечно же, на MSDN.
    Как известно, если при определении нового типа через существующие мы используем дополнительное ключевое слово type, то это значит, что старое и новое название будут совместимы по присваиванию. Но простите, скажите вы, как же будут совместимы по присваиванию строки в двух разных кодировках? Очень просто. Через автоматическое конвертирование. Отныне любое присваивание любой строки другой строке проходит через взаимную проверку их кодировок и автоматическую конвертацию в случае несовпадения. В случаях же, когда эта конвертация может вызвать потенциальную потерю данных (к примеру, UTF-16 => CP1252), генерируется соответствующий warning. Все присваивания, дабы максимально уберечь себя от потери данных, проходят через UnicodeString, то есть через UTF-16. Но всё-таки это ж не панацея.
    Вся работа со строками, которую вы использовали доселе, совершенно не изменилась. Всё так же строки состоят из символов, символы прекрасно доступны через индекс, начиная с единицы, всё так же изменяемы и форматируемы. Единственное, что теперь вы не всегда можете быть уверены, что SizeOf(Char) = 1, только и всего.


  2. Какие же доступны нововведения в RTL с поддержкой Юникода? Ну здесь я почти полностью всё сдеру со статьи на Developer Network (своими словами и вкратце), но вы можете с удовольствием почитать её в оригинале, все ссылки будут даны ниже.

    Во-первых, мы получили модуль Character со статическим классом TCharacter. Он даёт нам множество Unicode-поддерживающих функций для проверки символа, таких как TCharacter.IsLetter(), TCharacter.IsDigit(), то же для проверки суррогатных символов Юникода и т.д. Часть из них доступны и как независимые функции в модуле. По факту, прослеживается аналогия с property class в перловских регекспах.

    Во-вторых, статический класс TEncoding, основное предназначение которого заключается вот в этом наборе членов:
    class property ASCII: TEncoding read GetASCII;
    class property BigEndianUnicode: TEncoding read GetBigEndianUnicode;
    class property Default: TEncoding read GetDefault;
    class property Unicode: TEncoding read GetUnicode;
    class property UTF7: TEncoding read GetUTF7;
    class property UTF8: TEncoding read GetUTF8;
    class function GetEncoding(CodePage: Integer): TEncoding;

    Он необходим в тех местах, где надо явно указать кодировку. Так, например, отныне - в потоках, списках строк...
      ListBox1.Items.SaveToFile('MyListBoxItems.txt', TEncoding.UTF8);

    Стоит здесь ещё отметить, что в System.pas присутствует исключение EEncodingError.
    И, конечно же, все dbExpress-контролы также приобрели к себе в набор новое свойство Encoding.

    В-третьих, мы получили отличный дотнетовский TStringBuilder, с ускоренными операциями модифицированиями строки (Append, Insert, Delete, Replace, etc...), возвращающими получивший экземляр того же TStringBuilder, так что теперь имеет право на жизнь следующая конструкция:
    procedure TForm1.Button1Click(Sender: TObject);
    var
      MyStringBuilder: TStringBuilder;
      Price: double;
    begin
      MyStringBuilder := TStringBuilder.Create('');
      try
        Price := 1.49;
        Label1.Caption := MyStringBuilder.Append('The apples are $').Append(Price).Append(' a pound.').ToString;
      finally
        MyStringBuilder.Free;
      end;
    end;

    Или ещё того прелестнее... Вот кусок моего недавнего кода на C#:
    HTML = new StringBuilder(Regex.Replace(HTML.Append(new StringBuilder(new String(cmsg, 0, len)).Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace(" ", "&nbsp;").Replace("\t", "&nbsp;&nbsp;").Replace("\"", "&quot;").Replace("'", "&#39;").ToString()).Append("<hr color=\"gray\">").ToString(), @"((((ht|f)tp(s?):\/\/)|(www\.[^ \[\]\(\)\n\r\t]+)|(([012]?[0-9]{1,2}\.){3}[012]?[0-9]{1,2})\/)([^ \[\]\(\),;" + '"' + @"'<>\n\r\t]+)([^\. \[\]\(\),;" + '"' + @"'<>\n\r\t])|(([012]?[0-9]{1,2}\.){3}[012]?[0-9]{1,2}))", "<a href=\"$0\" target=\"_blank\">$0</a>")).Replace("\r\n", "<br />").Append("</font></body></html>");

    Если кто не понял, это всё одна строка :-)

    В-четвёртых... в-четвёртых здесь укажу по мелочи. По мелочи это функции StringElementSize(), StringCodePage(), BytesOf() и процедура SetCodePage. О их предназначении понятно из названий: соответственно "получить размер одного типичного символа данной строки", "получить кодовую страницу данной строки", "получить строку в виде массива байт" и "установить кодовую страницу данной строки" (с дополнительным логическим параметром "конвертировать прямо сейчас"). Всё просто и удобно.

    Что интересно, TEncoding и TStringBuilder интерфейсно совместимы с аналогичными классами .NET.


  3. Материалы для дополнительного чтения:
    Tiburón - String Theory
    Tiburon Unicode Video: #1, #2, #3, #4, #5
    Delphi in a Unicode World Part I: What is Unicode, Why do you need it, and How do you work with it in Delphi?
    Delphi in a Unicode World Part II: New RTL Features and Classes to Support Unicode
    Unicode database support in Tiburon for Delphi and C++


II. Generics.
Про generics (они же дженерики, они же обобщения, они же параметризированные типы, они же templates, они же шаблоны... хотя в последних двух случаях не совсем корректно так называть... они же абстракции параметрического полиморфизма... ну это вообще атас :-D ) на просторах программистского комьюнити в контексте Делфи было сказано уже немало. Впервые про данное явление люди заговорили в 2004-2005 годах, когда Microsoft выпустила на свет .NET 2.0, а Sun - J2SE 5.0, и появились первые серьёзные языки, которые были в состоянии спорить с зарекомендовавшей себя годами концепцией С++ных шаблонов. Прошло время... осенью 2007 выпускается Highlander - CodeGear RAD Studio 2007, включающая в себя Delphi.NET под 2-й фреймворк с поддержкой дженериков. Тогда коджировцы нам пообещали, что в следующей же версии эту возможность перенесут с полным функционалом под нативный Win32, что, насколько мне известно, беспрецедентное явление. Беспрецедентное потому, что это станет второй из используемых императивных нативных языков с поддержкой обобщённого программирования - после С++. В самом деле: обобщения вообще в мире поддерживает около десятка(!) хоть сколько-нибудь известных ЯП, из них C# и Java - ненативны, Ada и D - считайте, не используются, ну а про специфические фнукциональные Haskell и Scheme и говорить нечего... Только С++ был достойным гордым проводником света шаблонов в мире Win32. И теперь эту возможность перехватывает и Delphi :) Итак, читаем.

  1. Сначала про то, что же, собственно говоря, представляют собою дженерики... Это - класс, запись, интерфейс либо метод, зависящий от типа. То есть (рассуждаю на примере класса), имеем класс, который на этапе разработки "не знает", с каким типом ему придётся "иметь дело", однако вне зависимости от этого он реализует общее, абстрактное поведение для любого типа, который удовлетворяет определённым ограничениям. Если вы пишете сортировку, то какая разница, сортируются ли числа, строки или автомобили? Нам важно только, чтобы для данного типа была реализована или перегружена операция сравнения. Более того, если передать сортировщику ссылку на функцию сравнения, в лучших традициях всех сортировщиков (кстати, см. ниже об анонимных методах), то даже и сравнение становится неважные: за определение отношения порядка теперь будет отвечать программист. Второй пример: контейнеры. Список, стек, очередь, хэш-таблица, словарь, деревья хранят в себе экземпляры определённого типа... какая разница, какого? От них требуется общее поведение: добавлять данные, хранить данные, удалять данные, искать данные. Но довольно слов, перейдём к делу.
    Вот пример объявления обобщённого списка:
      TList<T> = class
      private
        FItems: array of T;
        FCount: Integer;
        procedure Grow(ACapacity: Integer);
        function GetItem(AIndex: Integer): T;
        procedure SetItem(AIndex: Integer; AValue: T);
      public
        procedure Add(const AItem: T);
        procedure AddRange(const AItems: array of T);
        procedure RemoveAt(AIndex: Integer);
        procedure Clear;
        property Item[AIndex: Integer]: T
          read GetItem write SetItem; default;

        property Count: Integer read FCount;
      end;

    Здесь мы описали класс TList, который зависит от одного определённого типа элементов, который там будет храниться. Поскольку мы не знаем на этапе написания, какой это будет тип, он условно назван Т. Ещё раз обращаю внимание, Т в контексте данного класса есть название типа, равноправное со словами Integer, String и т.д.; а за его пределами - не имеет никакого смысла! Ну это если кто не понял :)

    И как таким пользоваться, спросят люди? Очень просто.
    var
      ilist: TList<Integer>;
      slist: TList<String>;    
      IntElem: Integer;
      StrElem: String;

    begin
      ilist := TList.Create;
      try
        ilist.AddRange([1, 2, 3]); // ['1', 'second', 'third']);
        for IntElem in ilist do
          Write(IntElem, ' ');
        ilist.Clear;
      finally
        ilist.Free;
      end;

      slist := TList.Create;
      try
        slist.AddRange(['one', 'two', 'three']); // ['first', 'second', 'third']);
        for StrElem in slist do
          Write(StrElem, ' ');
        slist.Clear;
      finally
        slist.Free;
      end;

      Readln;
    end.

    Экземпляр такого класса уже не может быть обобщённым: мы создаём его, сообщая компилятору конкретный тип, который нам необходимо будет использовать здесь. И работаем с обычным списком чисел или строк, как если бы использовали два отдельных класса. Более того: на самом деле, если мне не изменяет память, оно так почти что и есть. Потому что вся работа по преобразованию типов выполняется непосредственно компилятором, и в тело вашего бинарника встраивается готовый код по работе отдельно с каждым используемым в программе типом. Получаем никакого падения в скорости работы и просто колоссальный подъём в скорости разработки.

  2. Развиваем тему. Что, если нам не нужна такая глубокая абстракция: целый класс, зависящий от одного типа? Отлично, можно конкретизировать это дело до отдельных методов. Банальнейший пример (сорри, примеры я копипастю, здесь использовался Delphi.NET; с точки зрения дженериков в Win32-версии всё идентично, а мелочные отличия заключаются в том, что не любой тип можно привести к TObject, но в данном случае для понимания идеологии это ещё лучше - потом, чуть ниже, станет ясно, почему):
    type
      // Generic methods - of non-generic class
      TFoo = class
        procedure GenericMethod<T>(aParam: T);
        function GenericFunction<T>: T;
      end;

    function TFoo.GenericFunction<T>: T;
    begin
      Result := Default(T)
    end;

    procedure TFoo.GenericMethod<T>(A: T);
    begin
      Writeln(TObject(aParam).ToString);
    end;

    var
      Foo: TFoo;
    begin
      Foo := TFoo.Create;
      Foo.GenericMethod<integer>(42);
      Foo.GenericMethod(13);
      Foo.Free;
    end;

    Два слова про функцию Default(). Как легко догадаться, не зная, что представляет собою тип, мы не можем и догадываться о возможных его значениях. Функция Default() помогает, когда нам требуется значение по умолчанию для этого обобщённого типа. Для целого типа она вернёт 0, для типа с плавающей точкой - 0.0, для строки - пустую строку (''), для указателя или класса - nil, для записи - экземпяр записи с обнулёнными подобным же образом полями... и так далее.

    Можно и разнообразить нашу жизнь посредством увеличения количества типов как в классе, так и в методе... и кто сказал, что они должны совпадать, между прочим? Here you are:
    type
      TGenericClass<T, U> = class
        procedure GenericMethod<V>(aParam1: T; aParam2: U; aParam3: V);
      end;

    procedure TGenericClass<T, U>.GenericMethod<V>(aParam1: T; aParam2: U; aParam3: V);
    begin
    end;

    Ну, общий принцип объявления ясен, да? В заключение ко второму пункту добавлю, что, помимо методов, принципы обобщения также расширяются на свойства (properties), записи (records), интерфейсы (interfaces), процедуры (procedures) и события (events). Приводить примеры синтаксиса здесь не имеет смысла, их будет чересчур много (и так уже пост разросся до гигантских масштабов, а впереди ещё как минимум столько же :-D), а принципы идеологии остаются идентичными. Едем дальше.

  3. Иногда есть потребность не просто указать, что это обобщение зависит от типа, а ещё и несколько уточнить, с какими типами его можно использовать, а с какими - нельзя. К примеру, взгляните на оговорку выше касательно примеров кода под .NET. Одним из отличий Delphi for Win32 является то, что примитивные типы в ней объектами не являются, следовательно, приведение TObject(aParam) далеко не всегда корректно. А только aParam - объект, то есть, другими словами, T - классовый тип. Отлично, давайте так и запишем:
    type
      TGenericRecord<T: class> = record
        procedure DoSomething(aParam: T);
      end;  

    procedure TGenericRecord<T>.DoSomething(aParam: T);
    begin
      Writeln(TObject(aParam).ClassName);
    end;

    Записью "T: class" я конкретизировал, что дженерик TGenericRecord принимает только классовые типы, то бишь при попытке объявить нечто вроде
    var
      WrongOne: TGenericRecord<Real>;
    компилятор заматерится на нас всеми карами преисподней, а вот
    var
      CorrectOne: TGenericRecord<TStringList>;
    съест как миленький))
    Привожу здесь полный (условно полный, некоторые из пунктов можно расширять до бесконечности) список доступных уточнений в виде параметров одного класса, с комментариями:
    type  
      TConstrainedGeneric<
        T;  // Без ограничений
        U: class;  // Классовый тип
        V: constructor;  // Тип, имеющий конструктор
        W: record; // Тип-запись
        X: TFoo;  // Наследник данного класса или сам он
        Y: class, IMyInterface;  // Класс, обязательно реализующий данный интерфейс
        Z: IGenericInterface<T>  // Обобщённый интерфейс, зависящий от того же типа, который в TConstrainedGeneric выступает первым
      > = class
      end;

    Таким образом, можно с уверенностью вызывать у параметра конструктор Create, не заботясь, что он может как просто не иметь конструктора, так и не подозревать о такой возможности; можно работать с членами параметра, будучи точно уверенным, что он расположен в стеке, а не в куче, и не надо заботиться о выделении под него памяти; можно вызывать функции определённого интерфейса, зная, что параметр 100% реализовал этот интерфейс...
    Одна только ложка дёгтя немножко разбавляет эту бочку мёда. Я нигде не нашёл упоминания об обратных ограничениях. То есть: какая синтаксическая конструкция должна указать, чтобы T обязательно не был классом? Что-то вроде "T: not class"? В D, если не ошибаюсь, подобное реализуется записью !(class T). Неизвестно. Непонятно. А ведь не может быть, чтобы подобного не предусмотрели, это ведь более чем существенная потеря гибкости...

  4. Материалы для дополнительного чтения (опять-таки, много дотнета, забейте на это :-D ):
    A Generic Playground, an intro to parameterized types in Delphi for .NET
    Highlander2 Beta: Generics in Delphi for .NET
    Tiburon - new language features for Delphi 2009
    Generics: Not Just for Lists
    Tiburon: fun with generics and anonymous methods
    Tiburón yeniliklerine devam ediyoruz-2 (Generics for Win32)
Ну и на закуску как постскриптум. Реализовав в Delphi for Win32 дженерики, команда CodeGear, естественно, не ограничилась этим, и добавила их широкое использование в RTL и VCL. В частности, в модуле Generics.Collections мы теперь увидим обобщённые версии TArray, TList, TStack, TQueue, TDictionary, TObjectList, TObjectQueue, TObjectStack, TObjectDictionary и TEnumerator. Это, конечно, далеко не STL, но всё же шажочек вперёд немаленький-с...


Оглавление:
 —> Part I: Юникод и параметризированные типы aka дженерики.
Part II: Анонимные методы, IDE и VCL, разнообразное прочее.
Part III: Нововведения в С++Builder`e.
Tags: delphi, tiburon
Subscribe

  • (no subject)

    Лучшее занятие холодным зимним воскресеньем — это немного покормить одного не сильно толстого, но все же тролля :)

  • Weaver — первые впечатления

    Это еще не мануал по языковым изменениям и пр., как прошлогодний с Тибуроном. Это просто мои первые впечатления после того, как я немножко…

  • Weaver

    Итак, вчера Embarcadero RAD Studio 2010 (Weaver) всё-таки релизнулся. Свои впечатления, наверное, выскажу подробно и обстоятельно (как всегда) после…

  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 8 comments