Большинство алгоритмических языков программирования, таких как Си и Паскаль, были созданы на рубеже 60 и 70-х годов. Иными словами, их возраст (за исключением Java) перевалил за третий десяток, что для компьютерной индустрии срок немалый. Они старше Internet, Windows да и собственно ПК не менее чем на десятилетие. Стоит отметить, что новые языки не переставали регулярно появляться, однако ни один из них не задержался в практике программирования, хотя приносимые ими идеи становились достоянием известных языков (как это произошло с объектно-ориентированным программированием).

Другой важной особенностью языкотворчества можно считать прекращение попыток создания «универсального» языка программирования, призванного объединить в себе все последние достижения в области разработки языков (можно вспомнить Алгол, PL/1 или Аду). Крупные «языковые» проекты безвозвратно ушли в прошлое вместе с порожденными ими языками.

Наконец, появление ПК и ОС с графическим интерфейсом (прежде всего Mac OS и Windows) привело к переносу внимания разработчиков ПО из сферы языков программирования в другие области средств разработки, такие как визуальное или объектно-ориентированное программирование, сетевые протоколы или модели баз данных. Сегодня программист использует в качестве инструмента не столько язык, сколько конкретную среду программирования (например, Delphi), а какой язык является базовым, не так уж важно.

Таким образом, интерес к созданию новых языков программирования снизился, а круг используемых стабилизировался. Можно считать, что в этой области «все сказано», и развитие средств разработки ПО пойдет дальше другими путями. Наступил удачный момент для анализа современных языков программирования и выяснения достигнутых практических результатов.

Сопоставительный анализ

Первое, что бросается в глаза при сравнении современных языков программирования, это их удивительное сходство между собой. Созданные в разное время, с разными целями, по разные стороны Атлантического океана, они в процессе своего практического использования обрастали разными полезными конструкциями и в конечном итоге пришли к почти полному тождеству. Приведем несколько примеров сходства конструкций одинакового назначения в разных языках программирования (используются языки Си и Паскаль, а также Java - в той части, где отличается от Си).

Цикл с параметром

В качестве первого примера воспользуемся оператором цикла с параметром (for). Это наиболее часто используемый составной (т.е. содержащий другие операторы) оператор в любом языке. Он всегда содержит следующие компоненты:

  • переменную - счетчик цикла
  • выражение - начальное значение цикла
  • выражение - конечное значение цикла
  • список операторов - тело цикла

Кроме того, цикл с параметром может содержать признак увеличения/уменьшения счетчика цикла (Паскаль) или способ модификации счетчика (Си и Java). Реализация на Паскале:

for i:=0 to max-1 do begin
arr[i]:=i;
end

Реализация на Си и Java:

for(i=0; i

Операторы цикла даже внешне выглядят одинаково. Цикл на Си более универсален, поскольку в нем можно задавать разные ограничивающие значения. В то же время цикл на Паскале проще реализуется и лучше соответствует «идеологии» цикла с параметром. Представляется, что различия в условиях окончания цикла больше соответствуют циклу с условием (while), чем циклу с параметром.

Структура типа «запись»

Следующий пример взят из раздела описания структур данных - это описание типа «запись» (структура в Си и Java). Подобная структура всегда содержит следующие компоненты:

  • список полей с указанием имени поля и его типа
  • список вариантных частей (union в Си)

Реализация на Паскале:

record
field1:integer;
field2:array[0..30]of char;
case byte of
1:(b0,b1:byte);
2:(i0:integer);
end

Реализация на Си:

struct {
int field1;
char field2[31];
union {
byte b0b1[2];
int i0;
} u;
}

Реализация на Java:

class {
int field1;
char[] field2;
int i0;
}

Java не содержит механизма для записи вариантных частей, а в Си нужно для каждой вариантной части придумывать собственное имя и тип. В то же время в Паскале требуется обязательно указать поле-переключатель или хотя бы тип этого поля (как в примере). Ни один из вариантов не представляется идеальным, однако наиболее универсальной можно считать реализацию на Паскале.

Синтаксис и семантика

Как видно из приведенных примеров, родственные конструкции разных языков программирования различаются главным образом «внешним видом»: набором ключевых слов или порядком следования компонентов. Содержимое практически полностью идентично. Небольшие различия, как правило, не имеют существенного значения и чаще всего являются «атавизмами» (как, например, возможность задания отличного от единицы модификатора счетчика цикла).

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

Если сравнить между собой родственные конструкции разных языков программирования с точки зрения семантики, то можно выделить «главные» компоненты конструкций, которые присутствуют во всех основных языках. Эти «общие» компоненты и есть та основа любого современного языка, которая прошла проверку временем и практикой.

Интересно отследить те немногие различия, которые есть в семантике современных языков программирования. Однако существуют и обратные примеры, когда средства, присутствующие только в одном языке программирования, полезны на практике. Например, в Си отсутствуют средства экспорта—импорта модулей (за исключением директивы #include), имеющиеся в современных реализациях Паскаля, Модуле и Аде. С другой стороны, Си содержит удобные механизмы создания строковых констант, отсутствующие в Паскале и родственных ему языках.

Стандарты языков программирования

Сравнивая между собой конструкции современных языков программирования и выделив их общую составляющую, можно описать (не создать, а именно описать уже существующий de facto!) «универсальный» язык программирования (правда, только на семантическом уровне).

Существующая ныне система стандартизации языков программирования не способствует выполнению этой задачи. Главная проблема состоит в том, что при описании стандарта семантическая составляющая не отделена от синтаксиса и лексики. Кроме того, при модернизации стандартов комитеты ISO/ANSI предпочитают скорее добавлять в язык новые возможности, чем исключать редко используемые, что приводит к неоправданному синтаксическому расширению языков.

На мой взгляд, необходимо предпринять несколько радикальных шагов, изменяющих создавшуюся ситуацию. Первое, что надо сделать, — отделить семантику каждой языковой конструкции (т. е. то, что она делает) от синтаксиса и лексики (т. е. от того, как она выглядит).

Второе — разработать единые правила описания семантики языка программирования (подобно тому, как с помощью БНФ описываются синтаксис и лексика языка). Можно предположить, что описание семантики конструкции языка должно содержать перечень ее обязательных компонент и описание действий. Порядок следования компонент и их внешний вид должны быть оставлены за пределами семантического описания. Ниже приведены примеры описания конструкций с разделением их семантической и синтаксической составляющих.

Семантическое описание оператора while. ЦиклСПредусловием должен содержать следующие обязательные компоненты:

  • УсловиеПродолженияЦикла
  • ТелоЦикла

УсловиеПродолженияЦикла представляет собой выражение логического (булевого) типа.

ТелоЦикла — оператор или список операторов языка программирования.

УсловиеПродолженияЦикла вычисляется всякий раз перед началом цикла. Если его значение «истина», то выполняются операторы, входящие в ТелоЦикла. В противном случае управление передается оператору, следующему за оператором ЦиклСПредусловием.

В семантическом описании ничего не говорится о том, как выглядит оператор, каков порядок следования его компонент. Внешний вид оператора содержится в синтаксическом стандарте конкретного языка программирования.

Синтаксическое описание оператора while. ЦиклСПредусловием выглядит следующим образом в разных языках программирования:

ЦиклСПредусловиемPASCAL::=
?while? УсловиеПродолженияЦикла ?do?
 ТелоЦикла 
ЦиклСПредусловиемC::=
?while? ?(? УсловиеПродолженияЦикла ?)?
 ТелоЦикла
ЦиклСПредусловиемMODULA::=
?while? УсловиеПродолженияЦикла ?do?
 ТелоЦикла ?end?

Семантическое описание типа «указатель». Подобная конструкция содержится в любом современном языке программирования (кроме Java, где указатели используются неявно).

ТипУказатель должен содержать следующие обязательные компоненты:

  • ИмяТипа
  • БазовыйТип

ИмяТипа представляет собой идентификатор.

БазовыйТип — имя встроенного или ранее описанного типа языка программирования.

ТипУказатель — это адрес конструкции БазовыйТип. Его размер фиксирован и зависит от платформы программирования (для Win32 он составляет 4 байта).

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

Синтаксическое описание типа «указатель». Синтаксические стандарты типа ТипУказатель есть в различных языках программирования (в Java указатели формально отсутствуют, но на самом деле все типы данных являются указателями).

ТипУказательPASCAL::=
ИмяТипа ?=? ?^? БазовыйТип
ТипУказательC::=
?typedef? ИмяТипа ?*? БазовыйТип
ТипУказательMODULA2::=
ИмяТипа ?=? ?pointer? ?to? БазовыйТип

Синтаксические описания этой конструкции в разных языках похожи друг на друга. Везде новому имени типа (идентификатору) ставится в соответствие имя другого типа (базового).

Содержание семантического и синтаксического стандартов языка

На основании приведенного выше можно в общих чертах описать способы задания семантического и синтаксического стандартов любого языка программирования. Семантическое описание любой конструкции языка (оператора, типа данных, процедуры и т. д.) должно содержать не менее трех обязательных частей:

  • Список компонент, из которых состоит конструкция (в ТипУказатель это компоненты ИмяТипа и БазовыйТип)
  • Описание каждой компоненты
  • Описание конструкции в целом

Для синтаксического описания достаточно привести формальное определение конструкции, например, в виде БНФ. Следует отметить, что со времен Алгола полное синтаксическое описание языка всегда присутствует в его стандарте. Раньше оно было вспомогательным средством, облегчающим работу разработчиков компиляторов, сейчас же это описание должно, во-первых, стать частью стандарта языка и, во-вторых, тесно интегрироваться с семантическим описанием.

Компиляторы с общей семантической базой

Описание единой семантической базы современных языков программирования даст уникальную возможность создания «универсального» компилятора, который мог бы работать с любым существующим языком, имеющим общую с другими семантическую базу.

Общая семантика позволит пользоваться единым семантическим анализатором и единым генератором исполняемого кода для всех используемых языков. Синтаксический анализатор также можно сделать универсальным с помощью формул БНФ. Лексика же всех современных языков почти идентична (например, правила записи числовых и строковых констант в таких разных языках, как Си и Паскаль, совпадают практически полностью).

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

Наконец, описание единой семантики современных языков программирования есть не что иное, как описание «универсального» (или, если угодно, «идеального») языка программирования, на создание которого потрачено столько сил, времени и средств в прошлом. Достаточно вспомнить работы IBM по созданию PL/1 или последний по времени проект министерства обороны США по созданию языка Ада. Непомерные амбиции потерпели фиаско, а искомый «универсальный» язык тем временем образовался сам по себе без каких-либо организационных усилий и финансовых вложений.

Язык будущего

Рис. 1. Области пересечения и объединения языков программирования

Хотя современные языки программирования похожи друг на друга, идентичность их далеко не полная. Каждый содержит конструкции, присущие только ему. Если мы попытаемся начертить схему пересечения семантики языков программирования, то можем получить изображение, приведенное на рис. 1. На нем видно, что существует общая семантическая зона, в которую входят конструкции, принадлежащие всем языкам программирования (или большинству из них). Таким образом, семантику каждого языка программирования можно условно поделить на «область пересечения» (конструкции общие для всех языков) и «область объединения» (конструкции специфические для данного языка). Поэтому создание входного языка для многоязычного компилятора можно произвести двумя различными способами:

1. Использовать только общие конструкции (область пересечения), отбросив все «особенные» конструкции языков, как не необходимые. Это приведет к усечению всех языков программирования.

2. Использовать все имеющиеся в языках конструкции (область объединения). В этом случае каждый из языков должен быть дополнен конструкциями, имеющимися в других языках программирования. Этот подход чреват чрезмерным расширением семантической базы.

Разумеется, ни один из этих подходов не должен применяться «в чистом виде», но более правильным представляется первый вариант, поскольку в «области пересечения» содержится исторически наработанный необходимый минимум семантических конструкций. Приведем несколько примеров из «области пересечения» и «области объединения» языков.

«Область пересечения», цикл с параметром

Цикл с параметром присутствует в любом языке программирования. Исторически это наиболее «старый» из операторов цикла — он существовал уже в ранних версиях Фортрана. Несмотря на огромное количество вариантов, он до сих пор сохранил свои основные черты. В нем имеется переменная-параметр (как правило, целого типа; в Паскале, Модуле 2 и современных версиях Си допустимо также использовать переменную скалярного типа), которая пробегает значения от начального до конечного с шагом 1 или более.

В языках, созданных под влиянием Паскаля (Модула 2, Ада), параметр цикла может изменяться только с шагом 1. В Си-подобных языках семантика цикла с параметром допускает не только любой шаг переменной, но и вообще любой оператор, произвольно изменяющий значение параметра. Кроме того, условие окончания цикла в Си может быть любым логическим выражением (а не только достижением переменной порогового значения). Иными словами, цикл с параметром в Си может вообще не содержать параметра (в этом случае он становится аналогом цикла while). Ниже приведены примеры одного и того же цикла:

Паскаль:	for i:=0 to max do inc(j);
Си, Си++, Java:	for(i=0; i<=max; i++) j++;
Модула 2:	for i:=0 to max do inc(j) end;

В данном случае граница между «областью пересечения» и «областью объединения» языка программирования может проходить внутри самой конструкции. Так, циклы с параметром из Си, имеющие целочисленный параметр и шаг 1, принадлежат к «области пересечения», а все другие варианты — к «области объединения».

«Область объединения», директива define в Си

Директива define присуща только языку Си. Она перекочевала из макроассемблера и задает макроподстановку, присваивая идентификатору некоторый текст (фрагмент программы). Это очень мощный инструмент (в сфере языков программирования мощность — не всегда положительное качество), используемый для нескольких различных целей. Упомяну следующие области использования define:

  • задание констант (наиболее распространенный вариант)
  • задание inline-функций
  • создание настраиваемых модулей (из других языков они есть только в Аде)

Директива define — безусловный анахронизм. Неслучайно в современных вариантах Си (Си++ и Java) ее область действия стараются ограничить, вводя дополнительные конструкции (директиву const и скалярные типы).

«Область объединения», тип «множество» в Паскале

Тип «множество» присутствует во всех языках Вирта — Паскале, Модуле и Обероне. В эту конструкцию можно добавлять элементы (целочисленные переменные) или удалять их, а также проверять наличие в ней любого элемента. Несмотря на очевидную полезность этой конструкции в определенных ситуациях, необходимым компонентом языка тип «множество» все же не является (он отсутствует не только во всех клонах Си, но и в Аде, созданном под сильным влиянием Паскаля).

Набор конструкций языка программирования будущего

Анализ «области пересечения» современных языков программирования может дать ответ на вопрос, какие конструкции наиболее жизнеспособны и, следовательно, сохранятся в дальнейшем.

Несомненно, ряд конструкций из «области объединения» также будет востребован в будущем, однако конкретный их перечень сможет определить только практика.

Структурный редактор

Стандартизация семантики языков программирования помимо возможности создания многоязычных компиляторов открывает и другие, не менее впечатляющие перспективы перед разработчиками. В частности, нужно уйти от написания текста программ, заменив текстовый редактор структурным. Почему это необходимо? Да потому, что язык программирования — не естественный язык.

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

ПЕРЕМЕННОЙ a ПРИСВОИТЬ 2.

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

Программа — не текст, а набор конструкций

С точки зрения компьютера программа вовсе не является текстом. Это набор операторов, описаний типов, процедур и т. д. Ошибки компиляции — не что иное, как результат непонимания друг друга компилятором и программистом: компилятор просит программиста объяснить, что тот имел в виду. Текст — не оптимальный способ представления программ: он неоднозначен и позволяет каждую конструкцию записывать разными способами.

Рис. 2. Цикл с параметром в структурном редакторе

Как только семантика языка программирования будет четко определена, появляется возможность отказаться от текста в качестве инструмента ввода программ; текстовый редактор уступит место структурному, позволяющему вводить сразу готовые структуры (операторы, типы данных). Например, ввод цикла с параметром может выглядеть, как заполнение диалоговой формы, содержащей поля для счетчика цикла, начального значения, конечного значения и списка операторов (рис. 2).

Преимущества структурного редактора

Переход к структурному редактору даст много преимуществ. Полностью (или почти полностью) исчезнут ошибки компиляции; ее скорость возрастет в несколько раз. Более того, станет возможна online-компиляция программы: код оператора создается прямо в момент его ввода, а создание исполняемого файла будет компоновкой уже откомпилированных частей.

Важной особенностью системы со структурным редактором станет практическое исчезновение понятия «язык программирования». При наличии широкой семантической базы (включающей помимо «области пересечения» часть «области объединения») переход с одного языка программирования на другой будет происходить простым переключением соответствующего параметра в настройках.

Псевдотекст

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

  • при импорте и экспорте фрагментов программ
  • при необходимости представить программу на экране компьютера и в твердой копии в привычном виде на каком-либо традиционном языке программирования

Разумеется, должны быть доступны и основные операции работы с текстом (главным образом, работа с блоками).

Перспективы семантического подхода

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

Переход на структурный редактор и отказ от текста как носителя программ позволит сделать следующий шаг после создания визуальных систем программирования. Визуальные системы (Delphi, Visual C и т. д.) уже сейчас позволяют создавать программы в интерактивном режиме. Однако при необходимости запрограммировать какой-нибудь алгоритм программист вновь работает с текстом программы. Структурный редактор ликвидирует эту необходимость действовать на двух уровнях абстракции.

В последние годы в области создания языков программирования наблюдается некоторый застой. Возможно, это затишье перед бурей. Стремительное развитие компьютерной индустрии не может не поставить перед создателями «средств производства» программ (компиляторов) новые задачи. Компиляторы должны стать «адекватны» эпохе визуального программирования и Internet. Унификация языков программирования и создание общепринятой семантической базы — необходимое условие продолжения прогресса в этой области ПО и в конечном итоге всей компьютерной индустрии в целом.

ОБ АВТОРЕ

Андрей Андреев - инженер-программист, разработчик бухгалтерского ПО, e-mail: strannik@mail.perm.ru


Содержание «универсального» языка программирования по основным разделам
ОператорыПрисваивания; вызова процедуры; условия (if) и выбора (case, switch); циклы с параметром (for), с предусловием (while), с постусловием (repeat или do-while)
Типы данныхЛогический тип (boolean); массив; структура (запись); скалярный; указатель; строка символов (необходим строгий контроль типов)
Механизм процедурКласс параметров var (в Си++ и Java); вызов функции как процедуры; Inline-процедуры; рекурсия прямая и косвенная
Механизмы раздельной компиляцииЕдиницы компиляции; наличие раздела описания модуля; директива импорта модуля; ограничения видимости идентификаторов; произвольная структура модулей
Объектно-ориентированное программированиеИнкапсуляция; наследование; конструкторы и деструкторы

Компилятор «Странник Модула-Си-Паскаль»

Первый многоязычный компилятор, поддерживающий три языка программирования с общей семантической базой.

Создает программы в среде Win32 (Windows-95-98-ME-NT-2000).

Распространяется бесплатно, с исходными текстами (15 000 строк на Модуле 2): www.download.ru/russian/programs/22_0.htm

4845