Почему я выбрала D?!

от автора

image
Так как это мой первый пост, то расскажу что уже довольно долго занимаюсь программированием игр на 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *