Главная
Новости
Строительство
Ремонт
Дизайн и интерьер




18.06.2021


17.06.2021


17.06.2021


17.06.2021


17.06.2021





Яндекс.Метрика

D (язык программирования)

07.03.2021

D (Ди) — мультипарадигмальный статически типизированный компилируемый язык программирования, созданный Уолтером Брайтом из компании Digital Mars. Начиная с 2006 года соавтором также является Андрей Александреску. D является потомком языка C++, но существенно доработан по сравнению с ним. Также он заимствует ряд концепций из языков программирования Python, Ruby, C#, Java, Eiffel.

D доступен для операционных систем Windows, Linux, Mac OS, FreeBSD. Ведётся работа по портированию на Android.

История

В предисловии к книге А. Александреску «Язык программирования D» Уолтер Брайт пишет, что начал разработку этого языка в 1999 году. Проект задумывался как реинжиниринг языка C++ с целью избавиться от наиболее существенных недостатков исходного языка и внедрить в него современные архитектурные решения. При создании языка D была сделана попытка соединить производительность компилируемых языков программирования с безопасностью и выразительностью динамических.

Первоначально автор предполагал назвать язык «Mars», но из-за преемственности по отношению к C++ в обсуждениях язык постоянно называли «D», в результате именно это название и закрепилось за проектом.

Стабильная версия компилятора 1.0 вышла 2 января 2007. Вскоре после выхода компилятора 17 июня 2007 года автор перевёл версию 1 в режим поддержки и приступил к разработке версии 2.0, которая изначально не гарантировала обратной совместимости. Эта версия (последняя на сегодняшний день мажорная версия D) развивается и по сей день.

Обзор языка

Синтаксис

D относится к семейству C-подобных языков, в общих чертах его синтаксис похож на C/C++/C#, Java. При разработке языка соблюдается принцип: код, одинаково валидный и в C, и в D, должен вести себя одинаково.

«Hello, world!» на D:

import std.stdio; void main() { writeln ("Hello, world!"); }

Так же, как в C, функция main() является точкой входа.

Конструкции if, for, while, do-while выглядят и работают аналогично C/C++. Инструкция множественного выбора switch выглядит аналогично C++, но допускает переменные в метках ветвей case и требует, чтобы каждая ветвь case завершалась break или return; для перехода на следующую ветвь после обработки текущей необходимо использовать специальную конструкцию goto case. Также запрещены конструкции switch без ветви default.

Из дополнительных управляющих конструкций можно отметить static if — инструкцию для условной компиляции (условие проверяется статически и в код включается содержимое той ветви, которая ему соответствует), оператор полного множественного выбора final switch — в отличие от обычного switch, он работает только со значениями enum, а компилятор статически проверяет, что в выборе учтены все возможные варианты и выдаёт ошибку в противном случае. Также есть цикл по коллекции foreach.

Модульность

В D встроена система разбиения программы на модули (пакеты), обеспечивающая раздельную компиляцию и контролируемый импорт-экспорт. Система пакетов напоминает принятую в Java или Go: пакеты образуют иерархическую структуру, естественно отображаемую на дерево файловой системы. В отличие от C++, в D нет глобального пространства имён, каждое имя определяется в каком-либо пакете. С помощью инструкции import модуль программы может импортировать пакет, сделав доступными все имеющиеся в нём определения. Обращение к импортированным именам может выполняться с квалификацией: «имя_пакета.имя_объекта».

Язык предусматривает ряд средств, направленных на обеспечение удобной работы с импортируемыми именами. Есть возможность переименования пакета при импорте, задания альтернативного имени (алиаса) импортируемого пакета, импорта конкретных имён. Кроме того, язык разрешает без каких-либо дополнительных инструкций использовать импортированные имена без квалификации именем пакета. Однако действует ограничение: если в области видимости есть более одного подходящего определения встреченного в программе имени, то компилятор выдаёт ошибку и требует, чтобы имя было явно квалифицировано. Это предотвращает так называемый «угон имён», когда при добавлении в списки импорта нового пакета компилятор начинает связывать некоторое имя в программе не с тем определением, с которым оно связывалось ранее.

Универсальный синтаксис вызова функций (UFCS)

В D реализован механизм UFCS (Uniform function call syntax), позволяющий вызывать функции для любого объекта так, как будто они являются его методами. Например:

import std.stdio; import std.algorithm; import std.array; void main() { auto a = [2, 4, 1, 3]; // все три следующих варианта корректны и работают одинаково writeln(a); // "классический" C-подобный вариант a.writeln(); // функция вызывается так, как будто является методом объекта "a", хотя и не является таковой a.writeln; // функцию без параметров можно вызывать без скобок // это позволяет использовать цепочки вызовов, характерные для функциональных языков int[] e = a.sort().reverse; // многострочная цепочка вызовов также возможна stdin .byLine(KeepTerminator.yes) .map!(a => a.idup) .array .sort; }

Атрибуты функций

Функции в D могут быть определены с дополнительными необязательными атрибутами, которые позволяют явно указывать некоторые аспекты поведения этих функций. Например, функция, помеченная атрибутом pure, гарантированно является функционально чистой (с некоторыми оговорками). Функциональная чистота при этом проверяется на этапе компиляции. Пример объявления функции с атрибутом:

pure int sum (int first, int second) { return first + second; } int sum (int first, int second) pure // атрибуты можно указывать и после списка аргументов { return first + second; }

Примеры атрибутов функций:

  • pure — функциональная чистота
  • @safe — гарантия безопасной работы с памятью
  • nothrow — функция гарантированно не генерирует исключений
  • @nogc — гарантия того, что функция не содержит операций, выделяющих память на сборщике мусора
  • @property — атрибут метода класса, позволяющий избежать использования «наивных» геттеров-сеттеров

Параллельные вычисления

В язык встроен механизм запуска параллельных подпроцессов с помощью встроенной функции spawn() и обмена данными между параллельно исполняемыми фрагментами кода путём передачи сообщений (функции send() и receive() / receiveTimeout()). Использование обмена сообщениями считается авторами D предпочтительнее обмена данными через общую память.

Тем не менее, в случаях, когда это необходимо (например, при передаче между сопрограммами больших объёмов данных) имеется возможность применять традиционный для императивных языков подход с разделяемыми областями памяти и синхронизацией доступа через семафоры и мьютексы. Для поддержки такого обмена:

  • c помощью модификатора shared могут объявляться данные, разделяемые между потоками, при этом на уровне компилятора блокируются попытки работать с этими данными не-атомарными операциями;
  • библиотека языка предоставляет стандартные синхронизационные примитивы;
  • библиотека языка предоставляет специальные блокировочные классы, которые используются для контекстной блокировки: объявление экземпляра такого класса в методе синхронизирует код от данной точки до конца метода;
  • с помощью модификатора synchronized можно объявить целый класс синхронизированным: все методы этого класса компилятор автоматически синхронизирует, так что никакие два из них не могут выполняться параллельно для одного и того же экземпляра класса, объявленного как shared.

Для всех встроенных средств синхронизации компилятор автоматически отслеживает и запрещает попытки изменять внутри синхронизируемого кода неразделяемые данные, доступные более чем одному потоку.

Встроенные юнит-тесты

В D юнит-тесты являются частью языка, их можно использовать без подключения дополнительных библиотек или фреймворков.

import std.stdio; int first (int[] arr) { return arr[0]; } unittest { int[] arr1 = [1, 2, 3]; int[] arr2 = [10, 15, 20]; assert(first(arr1) == 1); assert(first(arr2) == 10); } void main() { // ... }


В языке реализовано множество функций, отсутствующих в C++: контрактное программирование, встроенные юнит-тесты, модули вместо заголовочных файлов, сборщик мусора, встроенные ассоциативные массивы, замыкания, анонимные функции, значительно переработан механизм шаблонов.

Парадигмы программирования

D реализует пять основных парадигм программирования — императивное, ООП, метапрограммирование, функциональное программирование и параллельные вычисления (модель акторов).

Управление памятью

D использует сборщик мусора для управления памятью, однако возможно и ручное управление с помощью перегрузки операторов new и delete, а также с помощью malloc и free, аналогично C. Сборщик мусора можно включать и выключать вручную, можно добавлять и удалять области памяти из его видимости, принудительно запускать частичный или полный процесс сборки. Существует подробное руководство, описывающее различные схемы управления памятью в D для тех случаев, когда стандартный сборщик мусора неприменим.

SafeD

SafeD — название подмножества языка D, использование которого гарантирует безопасность доступа к памяти.

Типы данных

Язык имеет богатый набор определённых типов данных и средств для определения новых типов. Типы в языке D разделяются на типы-значения и типы-ссылки.

Базовые типы

Набор базовых типов можно разделить на следующие категории:

  • void — специальный тип для пустых значений
  • bool — логический тип
  • целочисленные типы: знаковые byte, short, int, long и соответствующие им беззнаковые ubyte, ushort, uint, ulong
  • типы для чисел с плавающей точкой: float, double, real. Для типов с плавающей точкой есть соответствующие им варианты для мнимых и комплексных чисел:
    • мнимые: ifloat, idouble, ireal
    • комплексные: сfloat, сdouble, сreal
  • знаковые (символьные) типы: char, wchar, dchar, обозначающие кодовые единицы кодировок UTF-8, UTF-16 и UTF-32 соответственно.

В отличие от C++ все размеры целочисленных типов определены спецификацией. То есть, тип int будет всегда размером 32 бита. Целочисленные литералы можно записывать в десятичной, двоичной (с префиксом 0b) и шестнадцатеричной (с префиксом 0x) системе счисления. Способ записи литералов в восьмеричной системе в стиле C (то есть с префиксом 0) был убран, так как такую запись легко спутать с десятичной. Если всё-таки нужно использовать восьмеричную систему, можно воспользоваться шаблоном std.conv.octal.

Производные типы

  • pointer — указатель
  • array — массив
  • associative array — ассоциативный массив
  • function — функция
  • delegate — делегат
  • string, wstring, dstring — удобные псевдонимы для неизменяемых массивов знаковых (символьных) типов immutable(char)[], immutable(wchar)[] и immutable(dchar)[], обозначающие неизменяемые (квалификатор immutable) строки Юникода в одной из кодировок UTF-8, UTF-16 и UTF-32 соответственно.

Пользовательские типы

  • alias — псевдоним
  • enum — перечисление
  • struct — структура
  • union — объединение
  • class — класс

Вывод типов, ключевые слова «auto», «typeof» и безымянные («Voldemort») типы

В D реализован механизм вывода типов. Это значит, что тип, как правило, может быть вычислен на этапе компиляции и его не обязательно указывать явно. Например, выражение: auto myVar = 10 на этапе компиляции будет преобразовано в int myVar = 10. Использование вывода типов дает несколько преимуществ:

  • Более лаконичный и читаемый код, особенно если в нём используются длинные имена структур или классов. Например, выражение

VeryLongTypeName var = VeryLongTypeName(/* ... */);

может быть заменено на

auto var = VeryLongTypeName(/* ... */);

  • с помощью ключевого слова typeof можно создать переменную такого же типа, как у существующей переменной, даже если её тип неизвестен. Пример:
// file1.d int var1; // file2.d typeof(var1) var2; // var2 получает тип int
  • использование безымянных типов. Пример:
// Функция фактически возвращает результат типа TheUnnameable, но, поскольку этот тип определен внутри функции, // мы не можем явно задать его как тип возвращаемого значения. // Тем не менее, мы можем задать возвращаемый тип как "auto", предоставив компилятору вычислить его самостоятельно auto createVoldemortType(int value) { struct TheUnnameable { int getValue() { return value; } } return TheUnnameable(); }

Безымянные типы неофициально называются Voldemort-типы по аналогии с Воланом-де-Мортом («Тот-Кого-Нельзя-Называть»), главным антагонистом серии о Гарри Поттере. Вывод типов не следует путать с динамической типизацией, поскольку хотя тип не задается явно, вычисляется он на этапе компиляции, а не во время выполнения.

Реализации

  • DMD — Digital Mars D (ВИКИ), эталонный компилятор, разрабатываемый Уолтером Брайтом. Этот компилятор наиболее полно реализует стандарт языка, поддержка всех нововведений появляется в нём в первую очередь. Фронт-энд и бэк-энд ([1]) распространяются под лицензией Boost.
  • LDC — DMD (ВИКИ) — фронт-энд для LLVM (САЙТ)
  • GDC — DMD (ВИКИ) — фронт-энд для компилятора GCC
  • LDC для IOS — LDC-based toolkit for cross-compiling to iOS
  • D для Android (недоступная ссылка) — Toolkit for cross-compiling to Android (x86 using DMD and ARM using LDC) инструкция
  • SDC (Глупый компилятор D) — экспериментальный компилятор (компилятор как библиотека), использующий LLVM в качестве бэк-энда и не основанный на DMD.
  • Calypso — LDC fork which provides direct Clang interoperability, allowing the use of C headers directly.
  • MicroD — DMD fork which outputs C source code instead of object files
  • DtoJS — DMD fork which outputs JavaScript source code instead of object files
  • D Compiler for .NET — A back-end for the D programming language 2.0 compiler. It compiles the code to Common Intermediate Language (CIL) bytecode rather than to machine code. The CIL can then be run via a Common Language Infrastructure (CLR) virtual machine.
  • DIL is a hand-crafted compiler implementation for the D programming language written in D v2 using the Tango standard library. The lexer and the parser are fully implemented. Semantic analysis is being worked on. The backend will most probably be LLVM.

Инструменты и средства разработки

IDE и редакторы

Поддержка D в различных IDE, реализованная с помощью плагинов:

Нативные IDE для языка D:

  • Coedit (Windows, Linux)
  • DLangIDE (Windows, Linux, OS X) — среда создана на самом D, поддерживает подсветку синтаксиса, автозавершение кода, DUB, различные компиляторы, отладку и многое другое.

D поддерживается во множестве текстовых редакторов: Vim, Emacs, Kate, Notepad++, Sublime Text, TextMate и других.

Менеджер пакетов

DUB — официальный менеджер пакетов D. DUB выполняет функции репозитория пакетов и используется для управления зависимостями, а также в качестве системы сборки. Набор зависимостей, метаданные о проекте и флаги компилятора хранятся в формате JSON или SDL. Пример простого файла проекта (JSON):

{ "name": "myproject", "description": "A little web service of mine.", "authors": ["Peter Parker"], "homepage": "http://myproject.example.com", "license": "GPL-2.0", "dependencies": { "vibe-d": "~>0.7.23" } }

Утилиты и инструменты

rdmd — утилита, идущая в комплекте с компилятором DMD, позволяющая компилировать и запускать файлы с исходным кодом D «на лету». Это позволяет использовать D для небольших программ аналогично bash, perl и python:

// myprog.d #!/usr/bin/env rdmd import std.stdio; void main() { writeln("Hello, world with automated script running!"); }

Вызов команды ./myprog.dв консоли автоматически скомпилирует и выполнит программу.

DPaste — онлайн-сервис для запуска программ на D в браузере, похожий на сервисы JSBin и CodePen.

run.dlang.io — онлайн-компилятор и дизассемблер.

Использование, распространение

Распространение языка D ограничено, но он применяется для реальной промышленной разработки ПО. На официальном сайте приводится список из 26 компаний, успешно использующих D в разработке программных систем, работающих в самых различных сферах, включая системное программирование, веб-проекты, игры и игровые движки, ПО для научных расчётов, утилиты различного назначения и прочее. Продвижением языка D занимается, в частности D Language Foundation — общественная организация, пропагандирующая сам язык D и открытое программное обеспечение, созданное с его использованием.

Согласно индексу TIOBE, максимальный интерес к D проявлялся в 2007—2009 годах, в марте 2009 года индекс языка D достиг 1,8 (12-е место), что является для него абсолютным максимумом. После снижения в первой половине 2010-х, к 2016 году он пришёл в относительно стабильное состояние — численное значение индекса колеблется в диапазоне 1,0-1,4, в рейтинге язык находится в третьей десятке. В рейтинге популярности, составленном по результатам агрегации данных о вакансиях разработчиков, язык D не входит ни в основной (top-20), ни в общий (top-43) список, что говорит о невысокой востребованности у работодателей.

Примеры кода

Пример 1

«Hello, world!»

import std.stdio; void main() { writeln("Hello, world!"); }

Пример 2

Программа, которая выводит аргументы командной строки, с которыми была вызвана

import std.stdio: writefln; void main(string[] args) { foreach (i, arg; args) writefln("args[%d] = '%s'", i, arg); }

Пример 3

Программа, которая построчно считывает список слов из файла и выводит все слова, которые являются анаграммами других слов

import std.stdio, std.algorithm, std.range, std.string; void main() { dstring[][dstring] signs2words; foreach(dchar[] w; lines(File("words.txt"))) { w = w.chomp().toLower(); immutable key = w.dup.sort().release().idup; signs2words[key] ~= w.idup; } foreach (words; signs2words) { if (words.length > 1) { writefln(words.join(" ")); } } }