лекции (2003) (Глазкова) (1160821), страница 21
Текст из файла (страница 21)
Пример:
FROM InOut
IMPORT WriteInt, WriteCh, Writeln;
Теперь можно писать WriteInt(d); при этом очень легко обнаружить соответствующее имя. Если имя без квалификации (не уточненное), то оно либо из предопределенной среды, либо из локальной среды, либо это имя входит в список импорта.
В современных ЯП применяется несколько другой вариант: если мы пишем квалифицированное имя (имя_модуля. имя), то компилятор должен сам догадываться, что в этот момент надо подгрузить соответствующую таблицу имен.
Следующий язык Н. Вирта Оберон очень много требует от программиста. В первой версии языка был модуль определений (интерфейс):
DEFINITION M;
...
END M
и модуль реализации:
MODULE M;
...
END M.
Использовалась только одна конструкция импорта:
IMPORT список_имен_модулей.
В следующих версиях языка было сделано радикальное упрощение: остался всего один вид модулей:
MODULE имя;
При этом структура пространства имен осталась той же, что и в Modula 2, но никакого разделения на определение и реализацию нет.
После имени, которое мы хотим экспортировать, в определении надо поставить *.
Пример:
TYPE STACK*=RECORD...END;
PROCEDURE PUSH* (...);
Не является ли такое упрощение очень жестким?
В эволюции современных ЯП мы видим ту же тенденцию.
В языке С++ можно было описывать объявление класса:
class x{
...
void f();
. . .
}
и отдельно реализацию:
void x::f() {...}
Т.е. в С++ было разделение определения и реализации.
В языках Java и C# разделения определения и реализации нет. Почему?
Тенденция 90-х годов: нельзя проектировать язык в отрыве от соответствующей интегрированной среды.
С современной точки зрения, текст программы - это документ для разработчика этой программы (под разработчиком понимается некоторый коллектив), а пользователь текст программы видеть не должен.
Идея следующая: в состав современных средств программирования обязательно должны входить специальные утилиты.
Например, в языке Оберон есть утилита, которая генерирует определения. По тексту модуля М она генерирует текст псевдомодуля определений:
DEFINITION M
TYPE STACK;
PROCEDURE PUSH;
...
END M
Это псевдомодуль: он подаётся не на вход компилятору, а разработчику.
Это есть средство документирования интерфейса соответствующего модуля. Для пользователя как раз доступен этот документированный код (при этом, его не должен писать сам программист-разработчик).
Также в языке Java есть специальная утилита javadoc, которая генерирует по модулю интерфейс класса: в него входят только public-члены класса.
Более того, javadoc умеет генерировать как просто текстовые файлы, так и файлы в формате html.
Пример:
class Х extends У;
Здесь javadoc вставляет гипертекстовую ссылку на раздел javadoc, в котором содержится определение класса У.
Т.о., по тексту программы автоматически генерируется браузер объекта.
Кроме того, есть концепция комментариев /**...*/, которые (в отличие от обычных) вставляются в текст документации.
Средства, позволяющие по тексту программы генерировать документирующую часть, есть и в языке C#.
При этом дизайн ЯП учитывает, что такие средства должны присутствовать.
В целом, мы имеем очень простую структуру области видимости, которая очень легко компилируется.
Лекция 16
Для определения новых типов данных в языке необходима возможность их определения на базе существующих (структуры, записи, массивы) плюс понятие процедурной абстракции. Поэтому новый ТД можно определить даже в таких языках, как Си.
Что необходимо в современных ЯП по сравнению с языком Си?
Более развитые средства защиты абстракций.
Мы говорили, что для определения новых типов данных необходима специальная конструкция, которая объединяет множество значений (структуры данных) и множество операций, при этом множество операций, в основном, и определяет тип данных.
В разных языках эта конструкция называется по-разному.
В одних языках - это понятие модуля (пакета), в других языках - это понятие класса.
Мы начали рассматривать модульные ЯП - это Modula 2, Delphi, Oberon, Ada.
Модуль состоит из двух частей:
-
интерфейс;
-
реализация.
В интерфейс входит описание структуры данных и прототипы множества операций.
Мы определили, что существует принцип РОРИ - разделение определения, реализации и использования.
Определения – это интерфейс.
Реализация - это тело определенных в интерфейсе операций и, возможно, вспомогательные процедуры и функции и описание вспомогательных типов данных. При этом реализация - локальная среда ссылок.
В соответствии с принципом РОРИ использование модулей всегда отделено от определения и реализации.
Мы отметили, что во всех рассматриваемых ЯП (за исключением Oberon) для интерфейса служат специальные конструкции:
В Модула 2 - это специальный модуль определений; в Delphi - это интерфейсная часть модуля. В этих языках было физическое разделение интерфейса и реализации. В языке Oberon интерфейс и реализация слиты воедино. Мы также говорили, что это - тенденция современных языков, но при этом принцип РОРИ в этих языках все равно работает, но только на уровне интегрированных сред.
Для каждого такого языка существуют специальные средства, которые входят в инструментальное окружение языка.
Тенденция современных языков такова: исходный код программы доступен только разработчику, а разработчику не нужно отделять определение от реализации, потому что он и определяет и реализует одновременно.
А вот пользователю исходный текст должен быть вообще не доступен. У него есть документация, и соответствие ее исходному тексту гарантируют специальные программы, называемые документаторами. Они генерируют интерфейс по исходному тексту программы.
Мы рассмотрели, что среда ссылок таких языков как Модула 2, Delphi, имеет вид:
предопределенная среда | ||||
М1 | M2 | ... | Mn | имена программных модулей |
... | локальная среда ссылок (среда реализаций) |
Глобальная среда ссылок определяется именами модулей плюс все имена, объявленные в интерфейсе.
Локальные среды ссылок (среды реализации) являются статическими, т.е. существует то время, пока модуль загружен в память.
Последний рассматриваемый модульный язык – язык Ада.
С точки зрения модульной организации, Ада самый сложный и мощный язык из всех языков, рассматриваемых в нашем курсе.
Основное понятие языка Ада – это пакет.
Пакет соответствует библиотечному модулю из языка Modula 2, либо unit из языка Delphi.
Пакет делится на две части:
-
спецификация пакета,
-
тело пакета.
Синтаксически это выглядит так:
-
спецификация пакета
package имя is
объявления
(объявления ТД, переменных, заголовки процедур и функций)
end имя;
-
тело пакета
package body имя is
объявления
определения процедур и функций
[begin
операторы]
end имя;
Часть begin операторы необязательная; она выполняется при загрузке пакета в память и служит для инициализации локальной среды ссылок.
В чем разница между Modula 2 и Ada?
Синтаксически все очень похоже.
Разница заключается именно в строении среды ссылок.
Если в Modula 2 программа состояла из линейной последовательности модулей (модули между собой взаимодействовали посредством конструкции IMPORT); в языке Ada пакеты могут вкладываться друг в друга. Считается, что все пакеты, которые есть в программном проекте, вложены в некоторый стандартный пакет, который называется STANDARD. Этот пакет и определяет предопределенную среду ссылок.
Т.о., вместо линейной последовательности модулей, которая образует глобальную среду ссылок, и одной предопределенной среды, в языке Ада среды являются вложенными друг в друга.
Пример
paсkage M1 is
...
package M11 is
...
end M11;
package M12 is
...
end M12;
end М1;
Где должны находиться тела вложенных в М1 пакетов М11; М12 и т.д.?
Они должны находиться в теле пакета M1.
package body M1
...
package body M11 is
...
end M11;
package body M12 is
...
end M12;
end M1;
Остается в силе правило: локальная среда ссылок тела пакета недоступна из вне, в отличие от имен, которые определены в спецификации пакета.
Локальные среды ссылок имеют вложенность. При этом порядок, в котором идут соответствующие тела несущественен, т.к. тела пакетов независимы друг от друга.
Заметим, что спецификации пакетов переставлять нельзя, потому что они определяют интерфейс (в частности, имена из М11 могут использоваться в М12).
Относительно использования имен, по сравнению с другими языками, в Ада ситуация более сложная.
Локальная среда ссылок (т.е. тело пакета) вообще не экспортирует никаких имен, а интерфейс пакета, естественно, экспортирует имена.
Но непосредственно видимыми являются только имена модулей, причем, начиная с точки, в которой они объявлены. Экспорт имен является потенциальным. К именам из спецификации пакета можно обращаться сразу после определения соответствующего пакета, но использовать эти имена можно только потенциально.
Пример
package P is
x: INTEGER;
package P1 is
x,y: T;
package P11 is
a: T2;
end P11;
end P1;
package P2 is
x,z: T1;
...
end P2;
в этой точке определены имена x,P1,P2; кроме этого определены имена x,y из Р1, x,z из Р2, а из Р11, но их можно использовать только через квалификацию (например, P1.x, P1.y, P2.x, Р2.z, P1.P11.a)
Заметим, что в других языках квалификация могла иметь только вид имя_модуля.имя (т.к. локальные среды ссылок имели только один уровень вложенности).
Так как в языке Ада допустима многоуровневая вложенность сред, то квалификация может иметь вид: имя_мод1....имя_модn.имя.
Зачем такое усложнение?
Когда мы будем рассматривать вопросы раздельной трансляции, мы увидим, что такая структура сред ссылок отвечает некоторым принципам технологии программирования и, в общем случае, является более мощной, чем линейная последовательность модулей, которая применяется в большинстве современных ЯП.
Внешне используемый принцип похож на принцип языка Oberon, где единственная возможность импорта – путем уточнения соответствующих имен.
Но все рассматриваемые в этом разделе ЯП обладают интересным свойством: в этих языках отсутствует возможность переопределения стандартных знаков операций. В языке Ада такая возможность есть.
Утверждается, что если правила описания имен такие, как мы только что рассмотрели (т.е. все имена, описанные в интерфейсе, экспортируются потенциально), то перекрытие стандартных операций не имеет никакого смысла.
Почему?
Пример
package Мath is
TYPE Vector is
FUNCTION “+” (X,Y: Vector) return Vector;
------|------ “*”-----|-----
------|------ “-”-----|-----
end Math
Пользоваться этими операциями очень удобно (например, A,B,C:Vector; A:=B+C;),
но имена А, В, С,+ видны только внутри пакета, где они определены (за пределами этого пакета они видны только потенциально).
Если мы хотим пользоваться операциями из пакета за пределами модуля Math, то
надо писать
A,B,C: Math.Vector
(если написать A,B,C:Vector; то компилятор выдает сообщение об ошибке, потому что имя Vector не видно непосредственно).
Обращаться к операции «+» теперь надо так:
A:=Math.”+”(B,C);
Т.о., все преимущество переопределения стандартных операций потеряно, т.к. более удобный для человека синтаксис потерян.
В реальной жизни приходится писать достаточно сложные выражения: векторные выражения типа A+(B*C-D)-Y.