лекции (2004) (1160823), страница 13
Текст из файла (страница 13)
BEGIN
//Операторная часть - может отсутствовать, используется для инициализации некоторых статических объектов.
Done = true;
END Stacks. // Эта часть может отсутствовать, но в нашем случае мы инициализируем переменную Done.
…// расписываются все процедуры, которые мы описали и т.д.
Видно, что понятие модуля не сводиться только к определению новых тд, в то же время оно позволяет удобно сгруппировать как определения самой структуры, так и операции.
2. В языке Дельфи есть главная программа:
-
Главный модуль
program имя;
....
begin
end.
-
библиотечные модули имеют вид:
Unit имя; //тоже самое, что имя библиотечного модуля в модуле 2
interface
объявления
implementation
объявления и тела функций
begin
операторы
end.
То что в Модуле 2 было разбито на два модуля, здесь присутствует в одном, хотя логическое разделение осталось. Модули создаются, если одни и те же понятия используются в разных местах программы.
Взаимодействие модулей.
Главное понятие - это экспорт и импорт (имён и совокупность их характеристик). Одно из главных понятий - понятие пространства имён и области видимости. Самая простая форма пространства имен в Си - общее пространство имён (для внешних имён), локальное пространство - для каждого файла, это читая логика языка ассемблер. В языке ассемблера были директивы EXTRN и PUBLIC - для импорта и экспорта переменных соответственно. В Си - директива static (переменная доступна внутри файла), иначе - на все файлы. Риск конфликта имен был большой – поэтому при разработках больших библиотек в первую очередь договаривались о префиксах.
XWindowSystem – объектно-ориентированная система графических интерфейсов написанная на Си
-
X lib – интерфейс нижнего уровня
-
X Toolkit – объектно-ориентированная нашлепка - набор интерфейсных элементов и правила их построения
-
Motif - система пользовательского интерфейса
Это всё было написано на Си (сотни тысяч строк кода). Все имена имели префиксы - X, Xt, Xm соответственно.
В Си++ для разрешения конфликта имен было введено понятие пространства имен. В языках Модула-2 и Дельфи вводится понятие видимости. Видимость была прямая (непосредственная) и потенциальная (через имя соответствующего модуля - через уточнение, квалификатор Qualify). В ассемблерном варианте у нас была только непосредственная видимость.
-
В Си уточнение имеет вид: имя::имя
-
В остальных ЯП: имя(модуля).имя
В модель видимости Дельфи и Модула-2 основано на том, что глобальное пространство имен структурируется на две части – непосредственно видимая и потенциально видимая. Вначале непосредственно видимы только имена модулей. Имя главной программы вроде как видимо, только зачем оно нам нужно – имена модулей нужный для экспорта, а из главного модуля ничего не экспортируем. Кроме того можем сказать, что имя главного модуля видимо, так как у нас не может быть другого модуля чье имя совпадало бы с именем главного модуля. Все остальные имена, которые появляются в модулях определений (или интерфейсной части модуля для Дельфи) являются потенциально видимыми. Для того, чтобы их импортировать у нас есть специальные конструкции.
-
В языке Модула-2 есть две конструкции
А)IMPORT список_имён_модулей;
Все имена из соответствующих модулей-определений становятся потенциально видимыми. Пример: IMPORT Stacks - получаем доступ к пространству имён модуля Stacks. Имена из модуля определений – это те самые имена из глобального пространства имен, которые становятся видимы потенциально, а имена из модуля реализации видимы только внутри.
S: Stacks.Stack;
Stacks.Init(S);
Stacks.Push(S, 1);
X := Stacks.Pop(S);
Б)Существует вторая форма импорта:
FROM имя-модуля IMPORT список_имён;
Мы указываем, какие имена, из какого модуля мы импортируем; они становятся непосредственно видимыми из текущего модуля. Если конфликтуют имена из разных модулей, то это не ошибка – здесь главное, чтоб не конфликтовали имена модулей. Конфликтующие имена внутри модуля не могут быть непосредственно видимыми – они видимы только потенциально. Если свое имя конфликтует с импортированным, то свое видимо непосредственно, а импортированное потенциально, если оба чужих имени – то оба потенциально.
2. В Дельфах проще - там только есть предложение типа: Uses список_имён_модулей;
Все имена из интерфейсной части модулей становятся видимыми непосредственно. Такой подход упрощает программирование, но значительно ухудшает читаемость больших программ. Пусть есть большая библиотека – и множество модулей, как определить какое имя из какого модуля.
Как действовать в подобной ситуации в Модуле 2:
-
просмотреть стандартные идентификаторы
-
если имя чужое – то оно появляется здесь – (FROM имя-модуля IMPORT список_имён) или оно появляться (имя_модуля.имя)
-
если свое – то описано в данном модуле
т.е. поиск ведется только по тексту текущего модуля.
Чтобы облегчить поиск в Дельфи включались интерфейсы всех модулей и утилита grep. Если имена из модулей конфликтуют между собой, то используется уточненный импорт – имя_модуля.имя_объекта, но такой импорт не используют если конфликта нет.
Языки программирования. Лекция 12.
Модульные языки (продолжение).
Напомним о том, что мы рассматриваем именно модульные яп, и определения новых тд опирается, прежде всего, на понятие модуля (см. лекция 11 о Модуле 2 и Дельфи). У нас есть глобальное пространство имен, в котором в начале непосредственно видимы только имена библиотечных модулей и главного модуля. Имена, которые определены в модуле определений (или интерфейсной части юнита для Дельфи), видимы потенциально. Локальными именами (видимыми только в пределах данного модуля) являются те, которые определены в модуле определений (implementation модуле). Пространство имен достаточно структурируемо. Есть специальная конструкция:
Uses список_имён_модулей; - для Дельфи
IMPORT список_имен_модулей; - для Модула 2
Разница конструкций в том, что uses сразу делает непосредственно видимыми все имена из соответствующих модулей, а Модуле 2 они видимы потенциально.
FROM или IMPORT список_имён; - вторая форма для Модулы 2, при таком обращении имена становятся непосредственно видимыми.
Требуется только чтоб имена модулей внутри одного проекта не конфликтовали, этого добиться не очень сложно. А если имена конфликтуют: если свое имя конфликтует с импортированным, то свое видимо непосредственно, а импортированное потенциально, если оба чужих имени – то оба потенциально (по средствам явного уточнения).
Отметим, что явное уточнение может быть неудобно при написании программ, зато повышает их читабельность. Такая модель принята во многих яп, например в Python, Оберон.
3)
Замечание 1.
В предварительной версии языка Оберон (1988 год) речь шла о двух видах модулей.
1.DEFINITION M;
...
ENDM.//модуль определений
2.MODULE M;
END M. // модуль реализации
Но в отличие от Модула 2 – отсутствует понятие главного модуля. Оберон - язык для некоторой операционной системы для некоторой рабочей станции (и система, и язык назывались Оберон). Есть понятие команда – это процедура без параметров, которая описана в каком-то модуле определений. С системой мы работаем в интерактивном режиме и если у нас есть модуль М и в нем есть процедура Р, то M.P – вызывая тем самым определенную команду, иными словами любая команда, написанная в любом модуле динамически подгружается, и автоматически становится доступной как интерактивная команда соответствующего shell’а. поэтому понятие главной программы просто отсутствует, поскольку в определенном смысле главной программой может стать любая процедура без параметров в соответствующем модуле определений.
В таком результате возникает некоторая избыточность, так как описанные типы данных и объекты дублировать в модуле реализаций не нужно, а заголовки процедур – нужно. Понятие модуля определений является избыточным, так как можно сделать по-другому - если объединить понятия модуля определения и модуля реализации. Так в окончательной версии языка (1990 год) – вся программа состоит только из модулей одного вида. Есть специальная конструкция - экспорт, то если у нас есть некоторый тип данных Т, который должен быть экспортирован, мы помечаем его звёздочкой (аналогично - в определениях процедур). Причем помечаем звездочкой в определении, каждое имя должно быть определено и только один раз.
O ~ MODULE M;
...
END M.
TYPE T* =
PROCEDURE P*(X:T)
Принцип «звездочки» (*) относится и к именам – рассмотрим пример с полями записи:
TYPE T*=RECORD //видно имя Т
X:INTEGER; // пользователь не имеет доступ, для языков с классами – аналогия с private и protected
Y*:REAL; //пользователь имеет доступ к имени У внутри типа Т, для языков с классами – аналогия с public
END;
Y*-:REAL; // Переменная доступна только для чтения, модифицировать ее могут только процедуры изнутри этого модуля.
Вирт объяснял такой подход тем, что понятие экспорта сводится к понятию проекция, т.е. на примере записи – все ее видимы части – это ее проекция. Таким образом, проекция не может быть шире исходного объекта, но может быть уже. Крайний случай – если открыто имя типа и закрыты все члены данных. В результате у нас всего одно понятие (модуль), а интерфейсная часть скрыта внутри модуля.
Тут есть некоторая тенденция. В старых ЯП, которые разрабатывались в 70-80года прошлого века, был реализован принцип языкового дизайна - Разделение Определения, Реализации и Использования (РОРИ). На примере Модула 2 мы видим – разделение определения, реализации и использования – в разделении программы на различные модули. Главной задачей модуля (кроме главного) является экспорт различных услуг - иногда принцип назывался РОРИУС (и УСлуги).
В языках этого времени раздельно было определение и реализация (уже рассмотрено в Модула 2, Дельфи, и еще увидим в Ада). В Пакетах языка Ада есть отдельно определение пакета и отдельно реализация. В языке Оберон (90-й год) понятие интерфейса, в явном виде как языковая конструкция, не существует - отказались от понятия модуля определения.
В языке Си++ поддерживается разделение реализаций.
-
class X {
int f(); // обязаны указать прототип, а тело ниже
} // описание класса
-
реализация класса – есть тела функций описанных в нем
inline int X::f() { ... } // реализация функции
В языках 90-х годов определение и реализация слиты воедино. В Си# и языке Java - если метод не абстрактный, то его реализация обязана присутствовать непосредственно в классе (что делает соответствующий класс трудночитаемым). Почему так произошло – в 90-х годах уже практически не разделяли язык и среду программирования. Первоначально языки проектировались без учёта того, что среда программирования возникает возле него.
Уже с конца 80-х годов начало учитываться то, что интегрированная среда программирования будет "обёртывать" ЯП.
То же самое относится и к понятию интерфейсов, т.е. во всех языках, про которые мы говорим, предполагается, что в системе программирования будет некоторое средство – документатор, которое умеет из модуля исходного текста «вылеплять» интерфейсы и представлять их в удобном и читаемом виде. Например, в ОС Оберон существует утилита, на вход которой идёт текст модуля и она генерирует интерфейсный модуль (псевдомодуль определений).
Пусть у нас есть тип Т, его поля Х и процедура Р, которые экспортируется, то псевдомодуль(не существует как конструкция для компилятора, но существует как единица документации) будет таким:
DEFINITION M;
TYPE T=RECORD
X:INTEGER;
END;
PROCEDURE P(X:T);
END M;
И Си# и языке Java есть специальные утилиты. JavaDoc, которая умеет по тексты Java-класса экстрагировать только те имена, которые являются публичными, т.е. относятся к определению, и представлять результат этого экстрагирования в виде усеченной Java- программы (либо в виде текста, либо в виде HTML). Таким образом получается документация программы.
class X {
public void f(X y) //кликнув на соответствующую ссылку – получаем информацию о функции, классе
}
Си# ориентирован на XML. В любом случае есть соответствующие утилиты, которые генерирует специальные документированные файлы, и включать средства и языковые конструкции в сам язык не нужно.
Современный подход к разработке яп не делает языки «замкнутыми», формулируются некоторые требования к среде программирования, а именно среда программирования должна иметь некоторое утилиты, которые достаточно быстро по тексту программы строят документацию. Текст программы рассматривается исключительно, как инструмент программиста, который разрабатывает этот проект, пользователи должны видеть не весь текст, а некоторые выжимки из него.
Замечание 2.