Так как это мой первый пост, то расскажу что уже довольно долго занимаюсь программированием игр на C++. Иной раз, окинув взглядом мое творение, я ужасаюсь. Виной тому шаблонное программирование — настолько запутанное и уродливое. Но, может существует более правильный и красивый путь? Многие пытаются ответить на этот вопрос и ищут альтернативу C++ в области «generic programming»… Я к примеру, перепробовала C#, Java, Python, JavaScript. Уже было решила писать на Lisp’е с его CLOS моделью, но тут я наткнулась на язык D, о нем и пойдет речь под катом.
Эффективность
Первое, что я проверила, было эффективность. Язык обладет собственным сборщиком мусора GC и у меня были сомения относительно автоматического управления памятью. Удивительно, но тесты показали, что D практически всегда быстрее C++, а на тестах с созданием множества мелких объектов, вроде векторов, обгонял в 2-3 раза (как потом я выяснил это сильная сторона любого хорошего сборщика мусора). Конечно с чистым С с включенной оптимизацией -O3 D проиграл (тесты проводились под Debian gcc), но не намного.
Надо заметить, для любителей оптимизации в ди есть встроенный переносимый ассемблер.
Также меня поразила скорость компиляции, она практически мгновенная. Благодаря этому язык может использоваться как скриптовый (есть стандартный интерпретарор rdmd), что позволило мне вообще отказаться в проектах от .sh и .batch файлов и внедрить кроссплатофрменные скрипты (о кроссплатформенности ниже).
Синтаксис
С эффективностью разобрались, а что насчет синтаксического сахара? По синтаксису D наиболее близок к С и Java, на нем можно сходу писать небольшие программы, даже не подозревая, что это D. Я не смогу рассмотреть все, поэтому отмечу самые важные моменты.
Не говоря уже о детской радости от использования вменяемого foreach (В других языках это уже давно), D использует технологию принципиально противоположную итераторам: Ranges. Благодаря очень мощным встроенным массивам, взятие подстроки реализуется очень просто:
string s = "some awesome string"; ... s = s[6..$]; assert(s == "awesome string"); // Думаю ассерты знакомы многим, о поддержке обработки ошибок ниже
При этом не создается новых массивов, в D массив является просто паком из двух указателей на начало и конец массива, что позволяет отлавливать выход за пределы и делать вот такой «slicing». Нужно упомянуть, что язык изначально поддерживает Unicode, но и имеет инструменты для работы с байтами. Вернемся к foreach и концепции Ranges, пример обработки введенного текста пословно:
import std.stdio; import std.algorithm; void main() { foreach (line; stdin.byLine()) foreach(word; splitter(line, " ")) { writeln(word); } }
D2 имеет очень мощную и хорошо документированную стандартную библиотеку Phobos (в эпоху D1 их было две, но это уже история), благодаря которой прикладные программки пишутся легко и с удовольствием.
В отличии от нового стандарта C++ в D2 есть полноценные лямбды с замыканиями и вывод типов, пример объяснит лучше:
void someFunc(double a1, dobule a2) { // Типы выводятся на этапе компиляции auto temp = new int[5]; // здесь мы передаем лямбду некой функции, которая будет использовать ее позже sendAlgorithm( (double arg) // возвращаемые типы выводятся автоматически { // мы имеем доступ к переменным, где была создана лямбда writeln(a1,"+",a2,"?=",arg); return a1+a2 == arg; }); }
Обработка ошибок
Язык имеет очень много инструментов для обработки ошибок, начиная от привычных исключений с try/catch/finally блоками, заканчивая встроенными юнит-тестами и контрактным программированием. Рассмотрим примеры:
// Две абсолютно одинаковых функций с классической обработкой исключений и scope выражением: void sendFile(Stream file) { try { // Выдуманная функция, которая кидает исключение sendByBlocks(file); } catch(Exception e) { writeln("Передача файла не удалась!"); } finally { // Вообще файл сам закроется, как только выйдет из зоны видимости, но это пример file.close(); } } void sendFile(Stream file) { scope(failure) // есть также success, который выполняется при отсутствии исключений { writeln("Передача файла не удалась!"); file.close(); } sendByBlocks(file); }
Практически везде можно объявить блок с юнит-тестами и провести доскональное испытание вашего кода:
unittest // В релизную версию тесты не попадают { // Ассерты позволяют легко проверить работоспособность функции и вывести корретное сообщение об ошибке assert(myFunc() == expectedResult, "Тест моей функции провален!"); }
Также очень полезно контрактное программирование, когда функция представляется в виде трех блоков: in, body, out. in и out проверяют входные и выходные параметры функции, очень полезно для интерфейсов, где объявлются только in out блоки. Все контракты вырезаются из релизной сборки приложения для ускорения работы.
// В языке отсутствует множественное наследование классов, но можно наследовать много интерфейсов как в Java interface SomeInterface { double someFunc(double a1, double a2) in { // Проверяем входные данные } out(result) { // Проверяем выходные данные } }
Обобщенное программирование
Теперь самое вкусное. В D расширили концепцию программирования на этапе компиляции, теперь любая функция с модификатором static может выполняться во время компиляции, а каждая функция имеет два! списка параметров, один ей передается в compiletime, другой в runtime.
// Тип T передается во время компиляции // ref обозначет, что аргументы переданы по ссылке void swap(T)(ref T a1, ref T a2) { T temp = a1; a1 = a2; a2 = a1; }
Также улучшена перегрузка обобщенных функций, теперь вместо придумывания всяких костылей можно делать вот так:
import std.traits; // Если guard выражение вернет false, то эта функция даже не рассматривается как кандидат для вызова string convert(T)(T arg) if( isFloatingPoint!T ) { //... } // Эта перегрузка будет вызвана только для класса myClass string convert(T)(T arg) if( is( T == myClass) ) { //... }
Хотя множественное наследование классов запрещено в D. Есть очень интересная возможность делать «примеси» (классические mixin из Scala):
// Эта функция может выполняться в compiletime static string constructField(T)(string name) { return T.stringof~" m"~name~";"; } class MyClass { // mixin внедряет строку как код на этапе компиляции // Результат: double mTime; // Знак "!" используется для указания compiletime аргументов mixin constructField!(double)("Time"); }
В стандартной библиотеке есть множество функций для получения списка типов параметров функции, списка всех методов класса, всех наследников класса и тому подобное, это то, чего мне жизненно не хватало в С++. Также ди поддерживает безопасные variadic функции:
// В функцию можно передать сколько угодно интов, все они упакуются в аккуратный массив int func1(int[] args...) { int temp; // у каждого типа есть свойство init, которым инициализируется переменная foreach(arg; args) temp += arg; return temp; } // Это уже интереснее, в функцию можно передать сколько угодно различных аргументов различных типов // все будет упаковано в специальный массив, такую нотацию использует writeln int func2(T...)(T args...) { foreach(i,arg; args) { // Типы аргументов тоже упакованы в массив writeln(i, " type: ", T[i].stringof, " ", arg); } }
Многопоточность
Язык перенял все положительные тенденции в современном многопоточном программировании. Используется модель языка Erlang, все потоки изолированы (даже имеют по своей копии всех глобальных переменных) и общение происходит через посылку ассинхронных сообщений. Также используются immutable типы, которые гарантированно никогда не меняются, например строки это immutable(char[]).
Однако можно явно объявить некоторые классы или переменные с модификатором shared, и компилятор будет вас доставать, когда идет явно ошибочное использование расшаренных данных, приводящее к дедлоками или к гонкам. Можно добавить к классу модификатор synchronized (Java подход) и к объекту будет привязан свой мьютекс, который следит за блокировками при входе и выходе из методов.
До D я не знала о существовании такого подхода, как locking-free programming, когда используются встроенные процессорные атомарные примитивы (check and set = cas), позволяющие вообще отказаться от блокировок, но это воистинну очень сложная техника (гораздо легче использовать immutable).
Кроссплатформенность
Она предоставляется из коробки. Любой платформозависимый код можно обернуть в специальные блоки version, в каждом из которых находится код для определенной платформы:
version(Windows) { ... } version(linux) { ... } version(MacOS) { ... } // Меток версий ос просто уйма, можно посмотреть на офф. сайте // также блоки версий можно использовать со своими константами для поддержки различных версий своего же софта
Стандартная библиотека кроссплатформенна, большинство существующих библиотек под D кроссплатформенные. Писать платформонезависимый код в D намного удобнее и быстрее чем в других языках.
Однако есть проблема с gui библиотекой, единственной вменяемой является порт gtk.
Все эти возможности открывают невиданные просторы для экспериментов и улучшения своего кода, при этом сохраняется читаемость и эффективность. В D есть еще несколько более хитрых фишечек вроде встроенная статическая диспетчиризация (при вызове несуществующего метода класса, компилятор вызывает специальны оператор в классе и передает ему строку-название метода). Тех, кто дочитал до этого момента, я искренне благодарю. Подробнее о языке можно узнать на оффициальном сайте(dlang.org) и из книги А.Александреску «The D Programming Language» (не знаю, появился ли перевод на русский). Далее пойдет речь о недостатках и проблемах языка.
Помимо достоинств, стоит также обратить внимание на недостатки:
Недостатки языка
- Первое, что замечаешь при переходе с C++, что D имеет довольно ограниченный инструментарий для взаимодействия с C++, а точнее D бинарно совместим только с C и тонны собственных библиотек приходится портировать на D вручную, однако я заметил тенденцию, что после портирования код на 50% компактнее и прекрастно читается.
- Отсутствие нормальных IDE, это удар ниже пояса. Язык еще очень молод и среды разработки все еще в альфа-бета версиях. Есть плагин для Visual Studio называемый VisualD, но автодополнение и анализ кода там очень хромают, есть D-IDE на .net — очень многообещающая среда, но сырая и только под Windows, есть DDT плагин для Eclipse с тяжеловесной проверкой синтаксиса, но у него довольно топорные настройки, которые мне не подошли. Однако вскоре понимаешь, что этот язык страдает не так сильно от отсутсвия IDE, сейчас пользуюсь Sublime Text 2 и я удовлетворена полностью, все сложности с поиском функций, классов и т.д. отошли к организации структуры сорцов и хорошей документации.
- Под виндой есть просто подводная гора, компилятор dmd гененрирует obj файлы в формате OMF, несовместимый с майкрософтовским COFF форматом. Поэтому у меня не получилось подключить, например, CUDA к проекту, потому что компилятор от Nvidia генерирует как раз COFF объектные файлы. Никакие ухищрения и конверторы не помогли. Под другими платформами такой проблемы вообще нет, так как линкует все файлы gcc. Из-за этого же различия форматов могут быть странные проблемы под виндой при передаче функций в сишные библиотеки, вплоть до падений приложения.
- Отсутствие огромного количества библиотек, однако все pure С и предоставляющие C интерфес библиотеки подходят для использования. На D все еще не существует хоть какого-нибудь трехмерного графического движка (что я пытаюсь исправить).
- Недавно я столкнулась с багом компилятора (что очень плохо) при генерирования shared library (.so) под 64 битную архитектуру, хоть все исходники компилятора и стандартной библиотеки открыты, существующие компиляторы C++, С, С# и т.д. гораздо стабильнее.
- Высокий порог вхождения. Язык полон различных фич и инструментов, не навязывает определенную парадигму программирования (я не рассказала о функционально программировании в D), поэтому требует от программиста четкого понимания, чего он хочет. Поэтому я бы не советовала язык новичкам.
Подведя итоги, мое мнение: D отличный язык как для системного, так и для прикладного программирования, но как технология еще довольно молодая и страдающая детскими проблемами. Сообщество вокруг языка маленькое, но активное. Я буду очень рада, если мой пост хоть как-то поможет развитию языка.
ссылка на оригинал статьи http://habrahabr.ru/post/159167/
Добавить комментарий