Modula-3

Один из главных постулатов философии Unix гласит: хороший инструмент для хорошего дела. Предположим, есть задача разработать большое приложение, которое должно иметь множественные потоки исполнения, возможно, быть распределенным и, естественно, располагать графическим интерфейсом. Хотелось бы сделать такую программу быстро и без лишних ошибок.

Полагаю, что первый из вопросов, которым следует задаться в подобной ситуации, звучит так: "Какой язык программирования подойдет?" С - неплохой выбор, но не для такого проекта. Он не очень хорошо масштабируется, а средств работы с процессами не имеет вовсе. Тогда С++? Но С++ довольно сложный язык, а из прошлого опыта известно, что порядочно времени уйдет на отладку проблем распределения памяти. Что же еще?

Есть хорошо спроектированный инструмент как раз для такой работы. Это язык Modula-3, разработанный и реализованный исследовательским центром Digital Equipment Corporation Systems Research Center (SRC). Modula-3 - современный, модульный, объектно-ориентированный язык. К другим его особенностям относятся автоматическое управление памятью (встроенный сборщик мусора), обработка исключений, поддержка динамических типов и средства многопоточного программирования.

Реализация SRC включает в себя компилятор, систему минимальной перекомпиляции (m3build) и широкий набор библиотек и примеров приложений. Нужно сказать, что SRC Modula-3 - бесплатная система, поставляемая с исходными текстами, включая компилятор и run-time ядро. Кроме того, SRC Modula-3 реализована для десятка платформ, включая Windows 95/NT.

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

Чтобы представить себе Modula-3, можно взять в качестве исходного пункта Pascal и переработать его для реального системного программирования. Строго говоря, непосредственными предками Modula-3 являются Modula-2+, Cedar и Euclid, более отдаленным - Simula. Новые, сравнительно с Pascal, возможности языка, по большому счету, служат либо для структурирования масштабных программных систем, либо обеспечивают возможности низкоуровневого (машинно-зависимого) программирования. Реальным приложениям нужно и то, и другое.

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

Кроме того, Modula-3 имеет объекты с единичным наследованием. Вокруг множественного наследования до сих пор идут споры, но фактом является то, что эта концепция может существенно усложнить и язык, и создаваемые с его помощью приложения, как концептуально, так и в реализации. В Modula-3 объект выглядит просто - запись с полями (состояние объекта) и набор методов (поведение объекта).

Другой из важнейших особенностей языка является сборка мусора. В последние годы ее перестали считать ненужной роскошью, осознав, что именно автоматическое управление распределением памяти служит гарантией надежности и живучести программ. Без этого программисту приходится постоянно решать, кто же будет "хозяином" памяти.

Например, если я передаю вам указатель на структуру, позволено ли вам сохранить этот адрес для ссылок в дальнейшем? Если да, то кто будет отвечать за освобождение памяти в туманном будущем: вы или я? Вопрос, когда можно безопасно освободить память, обычно решается ведением счетчика ссылок. Ну а теперь скажите сами, как часто программисты заключают и/или исполняют подобные соглашения в реальной жизни? И вообще, многие ли отдают себе отчет в существовании проблемы?

Результат, который мы повсеместно наблюдаем, - программы с массовыми протечками, подвисшими указателями и перекрестным использованием памяти в разных целях. Кроме того, даже когда все в порядке, в С выход из тела процедуры переходом по метке может привести к потере памяти. При обработке исключений С++ наблюдается та же проблема. То есть даже в штатных ситуациях, когда происходит досрочный выход из процедуры, ручное освобождение памяти становится проблематичным. Сборщик мусора решает все эти проблемы одним махом. А самое приятное, что сборщик в реализации SRC имеет отличную производительность. Это результат нескольких лет промышленной эксплуатации и настройки алгоритма.

Большинство современных программ и систем имеют, как минимум, налет многозадачности и асинхронности. Все графические приложения являются асинхронными по определению: ввод и управление ими осуществляется пользователем. Многопоточные и многопроцессорные приложения также существенно асинхронны. Поэтому не может не вызывать удивления факт, что очень немногие языки программирования имеют встроенные средства управления параллельным исполнением.

Многозадачность отдается на откуп программисту. Чаще всего программисты прибегают к использованию таймеров и обработчиков сигналов. Этот подход работает для совсем простых приложений и быстро терпит неудачу при росте сложности программ или когда программист пытается одновременно использовать две разных библиотеки, каждая из которых по-своему реализует многозадачность. Вам что-нибудь говорит название проблемы вложенных циклов обработки событий? Есть даже такая фраза: "Цикл обработки событий - это многозадачность для бедных".

Modula-3 имеет стандартные средства создания параллельных потоков исполнения, включая поддержку управления блокировками.

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

Ключом к повторному использованию кода являются так называемые общие (generic) интерфейсы Modula-3. Одним из главных их назначений является определение контейнерных типов, таких как стеки, списки и очереди. Общие интерфейсы позволяют сделать код, описывающий контейнерный объект, независимым от типа содержимого. То есть достаточно определить интерфейс "Таблица", чтобы конкретизировать его (instaniate) для целых или вещественных чисел, или для других типов данных. Параметризация типов и механизм шаблонов (template) в С++ имеют похожее назначение, но способ Modula-3 проще и яснее.

Наконец, несколько слов о низкоуровневом машинно-зависимом программировании. Один из важных уроков, которые язык программирования С преподал миру, заключается в том, что реальные системы требуют существенного объема программирования на машинном уровне. Так вот, возможность низкоуровневого программирования в Modula-3 есть.

Любой модуль с пометкой "unsafe" (опасный) имеет полный доступ к машинно-зависимым операциям. К их числу относятся адресная арифметика, ручное выделение и освобождение памяти, произвольное преобразование типов, машинно-зависимая арифметика и прочее. Эти возможности в полной мере демонстрирует реализация системы ввода-вывода. Ее нижний уровень вовсю использует машинно-зависимые операции для обеспечения производительности.

Впрочем, это еще не все. Строго говоря, провести границу между стандартными средствами языка Modula-3 и его прикладными библиотеками практически очень трудно. Поэтому вы можете переписать и ввод-вывод, и управление многозадачностью, и многое другое. Эта возможность ярче всех остальных характеризует мощь Modula-3 в роли языка системного программирования. В дополнение к сказанному следует упомянуть возможность использования библиотек, написанных на С, путем их импорта в качестве "опасных" интерфейсов.

Похоже, мы незаметно перешли к обсуждению системы программирования SRC Modula-3. Давайте ограничимся реализацией для Windows. Чтобы пользоваться Modula-3, следует установить определенное подмножество MS VC++ не ниже четвертой версии. Дело в том, что Modula-3 имеет компилятор, изощренную программу make (m3build), но пользуется штатным редактором связей VC.

Оболочки, или среды программирования, как таковой нет. Впрочем, в ней нет и особой нужды. Тексты программ создаются в текстовом формате любым подходящим редактором, пишется небольшой управляющий файл, где перечисляются модули программы и необходимые файлы ресурсов. После этого запускается m3build, который делает все, что нужно, причем никогда не перекомпилирует те модули, куда не вносились изменения. Кстати, собственного отладчика в реализации SRC Modula-3 для Windows тоже нет, но зато есть опция компилятора по включению отладочной информации, так что можно применять стандартный отладчик VC.

Хороший модульный объектно-ориентированный язык - это неплохо для начала, но, пожалуй, маловато, чтобы всерьез говорить о смене языка программирования. Во-первых, хотелось бы иметь уверенность в самом языке. Многого здесь не скажешь: или вы доверяете Digital Equipment и Olivetti, совместно разработавшим Modula-3 и поддерживающим его в рабочем состоянии уже более 5 лет, или нет.

Во-вторых, настоящую отдачу от языка обеспечивает наличие хороших стандартных библиотек с высоким коэффициентом повторного использования. Ответ таков, что это и есть сильная сторона системы программирования SRC Modula-3. Большая часть ее библиотек относится к классу "промышленных" и является плодом реальной эксплуатации и доработки. Кроме того, они документированы лучше, чем многие коммерческие библиотеки.

Libm3 - рабочая лошадка Modula-3. Это примерный аналог libc, стандартной библиотеки С, только значительно более богатый. Например, Libm3 описывает набор абстрактных типов для ввода/вывода, называемых "читателями" и "писателями". Они предоставляют абстрактный интерфейс потокового буферизованного ввода/вывода. В описываемую категорию попадают классические устройства stdin, stdout, и stderr. Кроме консольных, можно открывать также файловые потоки, а к типам 'File' и 'Terminal' добавлен еще и тип 'Pipe'.

Для записи в потоки используется интерфейс Fmt, который можно сравнить с printf в С. Одной из проблем С является то, что программист передает printf один тип данных, а пытается вывести его в формате другого типа. Интерфейс Fmt специально разработан так, чтобы сохранить гибкость printf, но не унаследовать проблем.

Далее, Libm3 имеет ряд общих интерфейсов, определяющих базовые контейнерные типы. К ним относятся таблицы, списки и последовательности. Таблица представляет собой соответствующим образом проиндексированный массив. Список напоминает списки в стиле Лиспа, а последовательность - это одномерный массив переменной длины.

Наконец, Libm3 предоставляет простой механизм сохранения под названием 'Pickle'. Припомните утомительную задачу преобразования сложных структур данных при записи их содержимого на диск или при чтении его с диска. Это настолько трудно и чревато ошибками, что многие программисты избегают подобной работы изо всех сил. Pickle вообще избавляет от нужды писать такой код. Поскольку исполняющая система и так "знает" раскладку структуры данных в памяти, то она и занимается записью данных в поток или извлечением их оттуда. Правда, если программист верит, что сделает эту работу лучше, он может ею заняться.

Далее, в состав реализации SRC Modula-3 входят библиотеки Trestle и VBTKit. Trestle уже упоминалась в связи с обсуждением многопоточных возможностей Modula-3. Ее использование существенно облегчает проектирование пользовательского графического интерфейса. Но для программиста Windows в этой библиотеке есть весьма существенное "но": она эмулирует интерфейс Х мира Unix! То есть приложение выглядит не "родным" для пользователя Windows. Правда, никто не мешает переработать эту библиотеку соответствующим образом...

VBTKit является инструментом более высокого уровня, построенным на базе Trestle. На цветных дисплеях он обеспечивает трехмерные интерфейсные объекты (кнопки, меню, диалоги и т.п.) в стиле Motif.

Библиотека FormsVBT в свою очередь построена на основе VBTKit. FormsVBT является системой управления интерфейсом пользователя. По существу, это простой язык описания раскладки (макета) пользовательского интерфейса и интерпретатор событий для этого языка. Кроме этого, библиотека обеспечивает программисту создание собственных процедур обработки событий (callback). Другими словами, FormsVBT предоставляет синтаксис описания графического интерфейса, а обработчики событий - его семантику.

Библиотека FormsEdit основывается на FormsVBT. Она предоставляет несложный интерактивный графический интерфейс для макетирования пользовательских интерфейсов. Как и следовало ожидать, она также воспринимает и отображает спецификации, написанные языком FormsVBT.

Библиотека M3TK относится к числу инструментов, с которыми ни мне, ни, вероятно, вам в "этой жизни" встречаться еще не приходилось. Она предоставляет средства для создания языково-зависимых инструментов. Например, с ее помощью можно разработать ПО управления исходными текстами программ проекта. В центре M3TK находится концепция абстрактного синтаксического дерева.

Иначе говоря, M3TK предоставляет систему метапрограммирования для Modula-3. В библиотеке есть полный набор средств синтаксического разбора исходных текстов языка, генерации синтаксического дерева и манипуляции им. Поэтому специфические для Modula-3 пакеты можно строить быстро и с относительной простотой. На деле генератор сетевых объектов, о котором пойдет речь ниже, был построен при помощи M3TK.

Нетрудно оценить, какое распространение получили сетевые распределенные системы в 90-х годах. Однако большинство языков практически не имеет средств поддержки распределенного программирования. Почти все приложения строятся прямо поверх сокетов, с применением простых средств потокового обмена (pipe) или с использованием интерфейса RPC. Проблема в том, что эти средства относятся к системным и плохо интегрируются в язык, что не может не создавать серьезных неудобств при создании распределенного приложения с цельной архитектурой.

Сетевые объекты (Network Objects) в реализации SRC позволяют экспортировать объекты в понимании Modula-3 через границы адресных пространств на одном или нескольких компьютерах. Исполняемая программа не имеет средств определить, где расположен реальный объект, с которым она работает: в ее собственном адресном пространстве, или он создан и находится в другом адресном пространстве на удаленном компьютере. Это очень мощное средство создания распределенных приложений.

Чтобы превратить объект Modula-3 в сетевой объект, нужно, чтобы он наследовал (прямо или опосредованно) от типа 'NetObj.T'. Объект не должен содержать полей данных. Интерфейс, содержащий описание объекта, затем обрабатывается специальным инструментом - генератором сетевого объекта, который генерирует весь код, нужный для отработки сетевых взаимодействий. Это все, что нужно для посылки объекта по сети. Совсем просто.

Поверх сетевых объектов построены две интересные системы, входящие в комплект SRC Modula-3. Первая из них - реализация языка Obliq. Obliq является объектно-ориентированным скриптующим языком с распределенной областью видимости. Создав объект Obliq, его можно передать на другой компьютер. Вторая система - Visual Obliq. Можете назвать ее "распределенный Visual Basic для Modula-3". Система включает интерактивный графический построитель (builder) приложений. Это очень мощное средство прототипирования и создания распределенных приложений.

Ну и наконец приведу мнение группы профессиональных разработчиков, работавших с С++, начиная с версии 1.2, - они считают Modula-3 отличным языком с прекрасными библиотеками! По их словам, интеллектуальных усилий для разработки программ требуется заметно меньше и практически всегда удается найти в библиотеках код, который может служить хорошей базой для реализации собственных процедур.

При написании статьи использованы материалы Digital Equipment Corporation Systems Research Center.

Евгений Щербатюк


Компьютерная газета. Статья была опубликована в номере 12 за 2000 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета