Языки программирования, 16 лекция (от 26 октября)
Материал из ESyr's Wiki pages.
Языки программирования 26.10.06
В прошлый раз мы остановились на межмодульных связях.
Библиотечный модуль Модулы дыва состоит из двух подмодулей, связанных одним именем.:
Модуль-интерфейс, гнде только определения
Модуль-реализация. Реализация и дополн объявления для неё, а так же инициализирующая часть.
Дельфи:
физически представляет собой один модуль, но присутствует понятие ирнтерфейся и реализации, разница чисто синтаксическая. Есть более тонкие моменты, которые отличают, они связаны с механизмом межмодульных связей.
Не разрешаются вложенные моудли.
Возникают два понятия: потенциальная и непоср видимость.
Непосредственна – конгда имя можно употреблять без уточнЯюзей информации
Протенцияалдьная – с уточняющей, указание имени модуля через точку. Везде через точку, только С++ выпендрился, там ::
Непосредственно ыидимыми являются только имена модулей. Все остальные имена потенциально видимые только те, что в разделе определений. Имена из модулей-определений видимы потенциально. Из модуля определений осуществляется экспорт в глобальное пространство имён.
Все имена равноправны.
Кроме понятие экспорта, есть понятие импорта. Импорт может быть как в модуль определений, так и вмоудль реализации. Таблицпа имён из модуля реализаций – конкатенация таблицы из модуля поерделений.
Модуль реализации только импортироет, как из модуля определений (всегда, неявно), так и из других модулей.
Есть две формы импорта:
IMPORT список модулей.
Стандартные и пользовательские модули абсолютно равноправны, при этом реальный ждоступ получаем тогда, когда делуем предожение IMPORT. Оно должно быьб первым среди всех объявлений. С точки зрения реализации компилятор должен просмотреть все папки поиска, и подкачать соотв таблицы имён. При этом соотв имена в этом модуле, если в нём есть предложение IMPORT, все имена из него ст ановятся видимы потенциально. Приходиться писать InOut.WriteLn. Программистов это раздражает, поэтому есть другая формаи импорта: FROM имя модуля IMPORT список идентификаторов – и имена становятся видимы непосредственно. Это сделано в дань программистом, которые ленятся набивать длинные имена. Беда непоср импорта в конфликте имён. Причём могут конфликтовать как с именами других модулей, так и с локальными. Локальные имеют больший приоритет (граждане первого сорта имеют больше прав, относительно иммигрантов). Это достаточно удобная модель.
Отличия Дельфи:
Имена из модульной реализации недоступны никому, но есть едиственная форма предложения импорта: uses список модулей – импорт непосредственный. Если имена конфликтуют между собой, то в этом случае непоср видимост снимается.
С первой точки зрения этот подход более улдобен, но когда лектор столкнулся с программированием в Дельфи и Модуле-2, то на Модуле-2 текст читать приятнее. Ибо когда в uses полтора десятка модулей, то дрогадаться, из какого модуля идентификатор, трудно. Поэтому Борланд всегда поставлял ос всеми своими компиляторами grep. А в Модуле-2 мы обречены на успех при поиске по файлу.
Порядок загрузки модулей поерделяется порядком зависимостей. В момент загрузки доступна инициализация – begin ... end. .
В этих языках есть принцип – РОРИ, Разделение
Определения
Реализация
Использования
Главенствовал в ЯП в 70-ч годах, когда появились ЯП с чёткой модульной структурой.
Есть ли в Си разделение определния и реализации? Это можно сделать, но ручками. И есть много подводных камней. Например, каждый хедер начинается с ifndef, дабы избежать повторного включения модулей.
#ifndef SYMBOL
#define SYMBOL
...
#endif
Интересно с этрой точки зрения посмотреть на язык Оберон, что же он упростил. История идёт по спирали, и там от РОРИ с первого взгляда отказалисб.
В первой версии языка:
DEFINITION M
опр-я
ENDM.
MODULE M
ENDM.
Модно выкинукть понятия модуля определения как такового. Вместо этого можно просто помечать. То же, что и в ассемблере.
MODULE M;
TYPE T* - если после имени стоит звёздочка, то оно экспортируемое
REOCRD
END;
PROCEDURE P* (X:T);
END;
ENDM.
На первый взгляд это отступление от РОРИ. Ибо программисту использующему нужен только интерфейс. На самом деле, в к концу 80-началу 90х годов получил распространение приём, что язык пишется как часть системы программирования.
//На 3 этаже стояли перфораторы
Прграммистдимеет делло с программой, которую просматривает в режиме онлайн.
С этой точки зрения интересен Eifel, который генерировал документирующие модули.
В современных ЯП (Си шарп, Джава) интерфейс и реализация не разделяются. На этот шаг пошли потому, что там есть программы, коотрые генерируют документацию по исходным кодам.
Наследование кода – Контрол-Ц – Контрол-В [:]||||||||||||[:]
Программисты не любят писать документацию, но теперь есть средства, которые генерируют её автоматически по комментариям.
В Обероне от Модулы осталось поняте Модуля, исчезли списки определений, но появились списки экспорта, и оставили только один способ импорта – IMPORT. Также отсутствует понятие главной проргаммы. Считается, что пользователь погружён в ОС, и модули экспортируют туда себя. Операционная среда Оберон предоставляет шелл, и пользователь может запускать комманды – экспортированные функции.
После педедыва язык Ада.
//педедыв
Стиль Си: использование префиксов. Что делать если имена конфликтуют имена – сплошной геморрой. Минимум, который необходим – структуризация модулей, хотя бы один уровень.
Язык Ада представдяет собой ультимативную модель.
Есть Пакет – модуль (не путать с джавой)
Пакет состоит из
Спецификации пакета
Тело пакета
package M is
объявления
end M;
package body M is
end M;
Функцию pop на аде написать нельзя, потому что у адской функции не должно быть побочного эффекта.
В Аде есть механизм импорта, но он только для раздельной трансляции.
Импорта как такового нет.
Есть пакет STANDART.
Прространства имён могут вкладываться – коренное отличие от других ЯП.
Спецификация пакетов – неявный список экспорта. Доступны они только потенциально.
Имена пакетов должны быть уникальными.
Явной конструкции импорта нет, ибо в других ЯП они нужны для раздельной трансляции.
Синтаксис: P1.P2. ... .PN
Интересный вопрос: нужна вложенность или нет.
В Аде есть такая штука, как перегрузка имён и функций, что есть в совр ЯП. Но в Аде можно было также перкрывать стандартные процедуры и функции, а акже знаки стандартных операций. Беда в том, что стандартные операции не префиксные (нектоторые неизвестно какие, например a[i]). Если бы вопрос был только о префиксных, то вопрос потенциальной видимости не мешает.
Пусть есть
package Vectors is
type Vector is
function «+»(x,y:Vector) return Vector;
end Vectors;
X, Y,Z:Vectors.Vector;
X:=Y+z – нельзя
vectors.«+»(y,z) – смысл?
//программисты, скрипя сердцем... [:]|||||||||||||||[:]\
Если попытаться на си написать A=B*exp(-i*C)*F(x), где всё комплексное, то математик скажет, что это не язык. Поэтому Страуструп добавил возможность добавления новых операторов.
И возникает вопрос, зачем тогда переопределение операторов если нет непоср области видимости? Поэтому в Аду есть uses список пакетов. Тогда можно для векторов писать Z:=X+Y. На первый взгляд всё замечательно. Но что делать, если возникате конфликт имён? Если X оперделён глобально, в M1, M2. И это приводит к программам, смысл которых от uses меняектся.
Проектирование систем:
top-down
bottom-up
Системы разбиваются на набор модулей, вопрос в том, с каком порядке разбивать.
Сверху вниз: разбить систему на модули, эти модули на ещё модули и т. д. Проблема в том, что делают заглушки. И тут мы тренируемся на лягушатнике, а когда бросают в бассейн – буль-буль
Снизу вверх. Модули нижнего уровня отлаживаются проще. Недостаток: заказчик не знает, что хочет, и меняет заказ, и видит готовую систему в самый последний момент.