Modula-3 — это язык программирования , задуманный как преемник модернизированной версии Modula-2, известной как Modula-2+ . Хотя он оказал влияние на исследовательские круги (оказав влияние на дизайн таких языков, как Java , C# , Python [8] и Nim ), он не получил широкого распространения в промышленности. Он был разработан Лукой Карделли , Джеймсом Донахью, Люсиль Глассман, Миком Джорданом (ранее в Olivetti Software Technology Laboratory), Биллом Калсовом и Грегом Нельсоном в Digital Equipment Corporation (DEC) Systems Research Center (SRC) и Olivetti Research Center (ORC) в конце 1980-х годов.
Основными особенностями Modula-3 являются модульность , простота и безопасность при сохранении мощности языка системного программирования. Modula-3 стремился продолжить традицию Pascal в отношении безопасности типов, одновременно вводя новые конструкции для практического программирования в реальном мире. В частности, Modula-3 добавил поддержку обобщенного программирования (похожего на шаблоны ), многопоточности , обработки исключений , сборки мусора , объектно-ориентированного программирования , частичного раскрытия и явного обозначения небезопасного кода. Целью разработки Modula-3 был язык, реализующий наиболее важные особенности современных императивных языков программирования в довольно простых формах. Таким образом, были опущены предположительно опасные и усложняющие особенности, такие как множественное наследование и перегрузка операторов .
Проект Modula-3 начался в ноябре 1986 года, когда Морис Уилкс написал Никлаусу Вирту несколько идей для новой версии Modula. Уилкс работал в DEC как раз перед этим моментом, а затем вернулся в Англию и присоединился к Совету по исследовательской стратегии Olivetti. Вирт уже перешел в Oberon , но не имел никаких проблем с командой Уилкса, продолжающей разработку под названием Modula. Определение языка было завершено в августе 1988 года, а обновленная версия — в январе 1989 года. Вскоре последовали компиляторы от DEC и Olivetti, а затем и сторонние реализации.
Его дизайн во многом был обусловлен работой над языком Modula-2+, который использовался в SRC и в исследовательском центре Acorn Computers Research Center (ARC, позже ORC, когда Olivetti приобрела Acorn) в то время, на котором была написана операционная система для многопроцессорной рабочей станции DEC Firefly VAX и на котором был написан компилятор Acorn для Acorn C и библиотека выполнения Modula (CAMEL) в ARC для проекта операционной системы ARX для линейки компьютеров Acorn Archimedes на базе ARM . Как указано в пересмотренном отчете Modula-3, на язык оказали влияние другие языки, такие как Mesa , Cedar , Object Pascal , Oberon и Euclid . [9]
В 1990-х годах Modula-3 приобрел значительную популярность как учебный язык, но он так и не был широко принят для промышленного использования. Возможно, этому способствовал упадок DEC, ключевого сторонника Modula-3 (особенно когда она перестала эффективно поддерживать его до того, как DEC была продана Compaq в 1998 году). В любом случае, несмотря на простоту и мощь Modula-3, похоже, что спрос на процедурный компилируемый язык с ограниченной реализацией объектно-ориентированного программирования был невелик . Некоторое время коммерческий компилятор CM3 поддерживался одним из главных разработчиков, работавших в DEC SRC, который был нанят до того, как DEC была продана Compaq , интегрированная среда разработки (IDE) Reactor и расширяемая виртуальная машина Java (лицензированная в двоичном коде и исходном коде и собираемая с помощью Reactor) предлагались Critical Mass, Inc., но эта компания прекратила активную деятельность в 2000 году и передала часть исходного кода своих продуктов elego Software Solutions GmbH. Modula-3 сейчас преподается в университетах в основном на курсах сравнительного программирования, а ее учебники больше не издаются. По сути, единственным корпоративным сторонником Modula-3 является elego, которая унаследовала исходные коды от Critical Mass и с тех пор выпустила несколько выпусков системы CM3 в исходном и двоичном коде. Reactor IDE была выпущена с открытым исходным кодом после нескольких лет отсутствия под новым названием CM3-IDE. В марте 2002 года elego также взяла на себя управление репозиторием другого активного дистрибутива Modula-3, PM3, который до этого поддерживался в Политехнической школе Монреаля, но впоследствии продолжил работу над HM3, совершенствовавшимся с годами, пока он не устарел.
Типичным примером синтаксиса языка является программа «Hello, World!» .
МОДУЛЬ Main ; ИМПОРТ IO ; НАЧАЛО IO . Put ( "Hello World\n" ) END Main .
Все программы на Modula-3 имеют по крайней мере файл модуля, в то время как большинство также включают файл интерфейса, который используется клиентами для доступа к данным из модуля . Как и в некоторых других языках, программа Modula-3 должна экспортировать модуль Main, который может быть либо файлом с именем Main.m3, либо файлом, который может вызываться EXPORT
для экспорта модуля Main.
МОДУЛЬ Foo ЭКСПОРТИРУЕТ Main
Имена файлов модулей рекомендуется совпадать с именем в исходном коде. Если они отличаются, компилятор выдает только предупреждение.
Другие соглашения в синтаксисе включают именование экспортируемого типа интерфейса T
, поскольку типы обычно квалифицируются их полными именами, поэтому тип T
внутри модуля с именем Foo будет назван Foo.T
. Это способствует читабельности. Другое похожее соглашение — именование публичного объекта, Public
как в примерах ООП ниже.
Прежде всего, все скомпилированные модули являются либо INTERFACE
реализациями MODULE
s, одного или другого вида. Скомпилированный модуль интерфейса, начинающийся с ключевого слова INTERFACE
, определяет константы, типы, переменные, исключения и процедуры. Модуль реализации, начинающийся с ключевого слова MODULE
, предоставляет код и любые дополнительные константы, типы или переменные, необходимые для реализации интерфейса. По умолчанию модуль реализации реализует интерфейс с тем же именем, но модуль может явно ссылаться EXPORT
на модуль с другим именем. Например, основная программа экспортирует модуль реализации для интерфейса Main.
МОДУЛЬ HelloWorld EXPORTS Main ; IMPORT IO ; BEGIN IO . Put ( "Hello World\n" ) END HelloWorld .
Любой скомпилированный модуль может иметь IMPORT
другие интерфейсы, хотя циклический импорт запрещен. Это можно решить, выполнив импорт из реализации MODULE. Сущности внутри импортируемого модуля можно импортировать, а не только имя модуля, используя синтаксис FROM Module IMPORT Item [, Item]*
:
МОДУЛЬ HelloWorld ЭКСПОРТИРУЕТ Main ; FROM IO IMPORT Put ; BEGIN Put ( "Hello World\n" ) END HelloWorld .
Обычно импортируется только интерфейс, и используется нотация «точка» для доступа к элементам в интерфейсе (аналогично доступу к полям в записи). Типичное использование — определение одной структуры данных (записи или объекта) на интерфейс вместе с любыми вспомогательными процедурами. Здесь основной тип получит имя «T», и используется как в MyModule.T
.
В случае конфликта имен между импортированным модулем и другим объектом внутри модуля AS
можно использовать зарезервированное слово, как вIMPORT CollidingModule AS X;
Некоторые возможности считаются небезопасными, когда компилятор больше не может гарантировать, что результаты будут согласованными; например, при взаимодействии с языком C. Ключевое слово UNSAFE
, предваряющее INTERFACE
или MODULE
, может использоваться для указания компилятору включить определенные низкоуровневые функции языка. Например, небезопасной операцией является обход системы типов с использованием LOOPHOLE
копирования битов целого числа в REAL
число с плавающей точкой.
Интерфейс, импортирующий небезопасный модуль, также должен быть небезопасным. Безопасный интерфейс может быть экспортирован небезопасным модулем реализации. Это типичное использование при взаимодействии с внешними библиотеками , где строятся два интерфейса: один небезопасный, другой безопасный.
Общий интерфейс и соответствующий ему общий модуль, префикс ключевого слова INTERFACE
or MODULE
с GENERIC
и принимают в качестве формальных аргументов другие интерфейсы. Таким образом (как шаблоны C++ ) можно легко определять и использовать абстрактные типы данных, но в отличие от C++ гранулярность находится на уровне модуля. Интерфейс передается в общий интерфейс и модули реализации в качестве аргументов, а компилятор сгенерирует конкретные модули.
Например, можно определить GenericStack, а затем создать его экземпляр с такими интерфейсами, как IntegerElem
, или RealElem
, или даже интерфейсами к объектам, при условии, что каждый из этих интерфейсов определяет свойства, необходимые универсальным модулям.
Голые типы INTEGER
или REAL
не могут быть использованы, поскольку они не являются модулями, а система дженериков основана на использовании модулей в качестве аргументов. Для сравнения, в шаблоне C++ будет использоваться голый тип.
ФАЙЛ: IntegerElem.i3
INTERFACE IntegerElem ; CONST Имя = "Integer" ; ТИП T = INTEGER ; ПРОЦЕДУРА Формат ( x : T ): TEXT ; ПРОЦЕДУРА Scan ( txt : TEXT ; VAR x : T ): BOOLEAN ; END IntegerElem .
ФАЙЛ: GenericStack.ig
ОБЩИЙ ИНТЕРФЕЙС GenericStack ( Element ); (* Здесь Element.T — тип, который будет сохранен в универсальном стеке. *) ТИП T = Public OBJECT ; Public = OBJECT МЕТОДЫ init (): TStack ; format (): TEXT ; isEmpty (): BOOLEAN ; count (): INTEGER ; push ( elm : Element . T ); pop ( VAR elem : Element . T ): BOOLEAN ; END ; END GenericStack .
ФАЙЛ: GenericStack.mg
ОБЩИЙ МОДУЛЬ GenericStack ( Element ); < ... подробности общей реализации ... > ПРОЦЕДУРА Формат ( self : T ): TEXT = VAR str : TEXT ; BEGIN str := Element . Name & "Stack{" ; FOR k := 0 TO self . n - 1 DO IF k > 0 THEN str := str & ", " ; END ; str := str & Element . Format ( self . arr [ k ]); END ; str : = str & "};" ; RETURN str ; END Формат ; < ... подробности общей реализации ... > END GenericStack .
ФАЙЛ: IntegerStack.i3
ИНТЕРФЕЙС IntegerStack = GenericStack ( IntegerElem ) END IntegerStack .
ФАЙЛ: IntegerStack.m3
МОДУЛЬ IntegerStack = GenericStack ( IntegerElem ) END IntegerStack .
Любой идентификатор можно отследить до места его происхождения, в отличие от функции «include» других языков. Скомпилированная единица должна импортировать идентификаторы из других скомпилированных единиц с помощью оператора IMPORT
. Даже перечисления используют ту же нотацию «точка», которая используется при доступе к полю записи.
ИНТЕРФЕЙС A ; ТИП Цвет = { Черный , Коричневый , Красный , Оранжевый , Желтый , Зеленый , Синий , Фиолетовый , Серый , Белый };КОНЕЦ А ;
МОДУЛЬ Б ;ИМПОРТ А ; ИЗ А ИМПОРТ Цвет ;VAR aColor : A . Color ; (* Использует имя модуля в качестве префикса *) theColor : Color ; (* Не имеет имени модуля в качестве префикса *) anotherColor : A . Color ;BEGIN aColor : = A.Color.Brown ; theColor : = Color.Red ; anotherColor : = Color.Orange ; ( * Нельзя просто использовать Orange * ) END B.
Modula-3 поддерживает выделение данных во время выполнения . Существует два типа памяти, которые могут быть выделены, TRACED
и UNTRACED
, разница в том, видит ли ее сборщик мусораNEW()
или нет. используется для выделения данных любого из этих классов памяти. В UNSAFE
модуле DISPOSE
доступен для освобождения неотслеживаемой памяти.
Методы объектно-ориентированного программирования могут использоваться в Modula-3, но их использование не является обязательным. Многие другие функции, предоставляемые в Modula-3 (модули, дженерики), обычно могут заменить объектно-ориентированное программирование.
Поддержка объектов намеренно сведена к самым простым терминам. Тип объекта (называемый «классом» в других объектно-ориентированных языках) вводится с OBJECT
объявлением, которое по сути имеет тот же синтаксис, что и RECORD
объявление, хотя тип объекта является ссылочным типом, тогда как RECORD в Modula-3 таковыми не являются (аналогично структурам в C). Экспортируемые типы обычно именуются T по соглашению и создают отдельный тип «Public» для раскрытия методов и данных. Например:
ИНТЕРФЕЙС Person ; ТИП T < : Public ; Public = МЕТОДЫ ОБЪЕКТА getAge (): INTEGER ; init ( name : TEXT ; age : INTEGER ): T ; END ; КОНЕЦ Человек .
Это определяет интерфейс Person
с двумя типами, T
, и Public
, который определяется как объект с двумя методами, getAge()
а init()
. T
определяется как подтип Public
с помощью оператора <:
.
Для создания нового Person.T
объекта используйте встроенную процедуру NEW
с методом init()
as
VAR jim := NEW ( Person . T ). init ( "Джим" , 25 );
Конструкция Modula-3 REVEAL
обеспечивает концептуально простой и чистый, но очень мощный механизм сокрытия деталей реализации от клиентов с произвольно большим количеством уровней дружественности . Полное раскрытие формы REVEAL T = V
может быть использовано для демонстрации полной реализации интерфейса Person
сверху. Частичное раскрытие формы REVEAL T <: V
просто показывает, что T является супертипом V, не раскрывая никакой дополнительной информации о T. [10]
МОДУЛЬ Человек ;REVEAL T = Public BRANDED OBJECT name : TEXT ; (* Эти две переменные *) age : INTEGER ; (* являются закрытыми. *) OVERRIDES getAge := Age ; init := Init ; END ;ПРОЦЕДУРА Возраст ( self : T ): ЦЕЛОЕ ЧИСЛО = НАЧАЛО ВОЗВРАТ self . age ; КОНЕЦ Возраст ;ПРОЦЕДУРА Init ( self : T ; name : TEXT ; age : INTEGER ): T = BEGIN self . name := name ; self . age := age ; RETURN self ; END Init ;НАЧАЛО КОНЕЦ Человек .
Обратите внимание на использование BRANDED
ключевого слова, которое «маркирует» объекты, делая их уникальными, чтобы избежать структурной эквивалентности. BRANDED
может также принимать строку в качестве аргумента, но если оно опущено, для вас генерируется уникальная строка.
Modula-3 — один из немногих языков программирования, который требует, чтобы внешние ссылки из модуля были строго квалифицированы. То есть, ссылка в модуле A
на объект, x
экспортируемый из модуля, B
должна иметь вид B.x
. В Modula-3 невозможно импортировать все экспортируемые имена из модуля.
Из-за требований языка к квалификации имени и переопределению метода невозможно сломать работающую программу, просто добавив новые объявления в интерфейс (любой интерфейс). Это позволяет многим программистам редактировать большие программы одновременно, не беспокоясь о конфликтах имен; и это также позволяет редактировать основные библиотеки языка с твердым знанием того, что ни одна существующая программа не будет сломана в этом процессе.
Обработка исключений основана на системе блоков TRY
... EXCEPT
, которая с тех пор [ требуется ссылка ] стала общепринятой. Одна особенность, которая не была принята в других языках [ требуется ссылка ] , за исключением Delphi , Python [1], Scala [2] и Visual Basic.NET , заключается в том, что EXCEPT
конструкция определяет форму оператора switch с каждым возможным исключением как случаем в собственном предложении EXCEPT. Modula-3 также поддерживает конструкцию LOOP
... EXIT
... END
, которая выполняет цикл до тех пор, пока не EXIT
произойдет ... , структура, эквивалентная простому циклу внутри предложения TRY
... EXCEPT
.
Язык поддерживает использование многопоточности и синхронизацию между потоками. В библиотеке времени выполнения ( m3core ) есть стандартный модуль Thread, который поддерживает использование многопоточных приложений. Среда выполнения Modula-3 может использовать отдельный поток для внутренних задач, таких как сборка мусора.
Встроенная структура данных MUTEX
используется для синхронизации нескольких потоков и защиты структур данных от одновременного доступа с возможным повреждением или условиями гонки. LOCK
Оператор вводит блок, в котором заблокирован мьютекс. Разблокировка a MUTEX
подразумевается выходом локуса выполнения кода из блока. Это MUTEX
объект, и, как таковой, из него могут быть получены другие объекты.
Например, в разделе ввода/вывода (I/O) библиотеки libm3 читатели и писатели (Rd.T и Wr.T) являются производными от MUTEX и блокируют себя перед доступом или изменением любых внутренних данных, таких как буферы.
Подводя итог, можно сказать, что язык обладает следующими особенностями:
Modula-3 — один из редких языков, эволюция функций которого задокументирована.
В разделе «Системное программирование с Modula-3» интенсивно обсуждаются четыре основных момента проектирования языка. Это следующие темы: эквивалентность структур и имен, правила подтипирования, универсальные модули и режимы параметров, такие как READONLY
.
Продолжая тенденцию, начатую с языка C , многие из функций, необходимых для написания реальных программ, были исключены из определения языка и вместо этого предоставлялись через стандартный набор библиотек . Большинство интерфейсов ниже подробно описаны в [11]
Стандартные библиотеки, предоставляющие следующие возможности. Они называются стандартными интерфейсами и требуются (должны быть предоставлены) в языке.
TEXT
sMUTEX
переменную условия и приостановку потоков. Библиотека потоков обеспечивает упреждающее переключение потоковНекоторые рекомендуемые интерфейсы реализованы в доступных реализациях, но не являются обязательными
Как и в C, ввод-вывод также предоставляется через библиотеки, в Modula-3 называемые Rd
и Wr
. Объектно-ориентированный дизайн библиотек Rd (readers) и Wr (writers) подробно описан в книге Грега Нельсона. Интересным аспектом Modula-3 является то, что это один из немногих языков программирования, стандартные библиотеки которых были формально проверены на отсутствие различных типов ошибок, включая ошибки блокировки. Это было сделано под эгидой проектов Larch/Modula-3 (см. семейство Larch ) [12] и Extended static checking [13] в исследовательском центре DEC Systems .
Доступно несколько компиляторов, большинство из которых имеют открытый исходный код .
Поскольку единственным аспектом структур данных C, отсутствующим в Modula-3, является тип объединения, все существующие реализации Modula-3 способны обеспечить хорошую совместимость двоичного кода с объявлениями типов массивов и структур языка C.
Ни одна из этих книг до сих пор не переиздана, хотя доступны подержанные экземпляры, а некоторые из них частично или полностью оцифрованы, а некоторые главы одной из них имеют предыдущие или последующие версии, доступные в виде исследовательских отчетов в Интернете.
Программное обеспечение, разработанное с помощью Modula-3, включает в себя:
Хотя Modula-3 не получил статуса основного языка, несколько частей дистрибутива DEC-SRC M3 получили его. Вероятно, наиболее влиятельной частью была библиотека сетевых объектов, которая легла в основу первой реализации удаленного вызова методов (RMI) Java, включая сетевой протокол. Только когда Sun перешла от стандарта Common Object Request Broker Architecture (CORBA) к протоколу на основе IIOP, она была отменена. Документация Java по сборке мусора удаленных объектов по-прежнему ссылается на новаторскую работу, проделанную для сетевых объектов Modula-3. [21] Реализация классов в Python также была вдохновлена механизмом классов, найденным в C++ и Modula-3. [22] Также язык Nim использует некоторые аспекты Modula-3, такие как трассируемые и нетрассируемые указатели.