В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 12
Текст из файла (страница 12)
Для начала подчеркнем, что в качестве составляющих единого аппарата
связывания в системе программирования естественно рассматривать редактор
связей, загрузчик, оптимизатор, компилятор, интерпретатор и другие системные
средства, предназначенные для подготовки программы к выполнению. Ведь все
эти средства участвуют в различных этапах окончательного связывания
конкретных операций с конкретными операндами.
Итак, можно говорить о связывании загрузочных модулей для последующего
совместного использования. Такая работа выполняется редактором связей. Можно
говорить о связывании аргументов подпрограммы с ее телом для последующего
совместного выполнения. Такая работа выполняется вызовом и заголовком
подрограммы. Вполне разумно говорить и о связывании отдельных компонент
объектной программы в процессе ее трансляции с ЯП. Такая работа выполняется
транслятором.
Отметим важный аспект. Связывание может распадаться на этапы,
выполняемые на различных стадиях подготовки того конкретного акта
исполнителя, ради которого это связывание в конечном итоге осуществляется.
Например, транслировать можно за несколько проходов; связать загрузочные
модули можно частично редактором связей, частично загрузчиком; при обращении
к программе часть работы по связыванию можно выполнить при обращении к
подпрограмме, часть - при выполнении ее тела (именно распределением работы
по связыванию отличаются различные способы вызова параметров -
наименованием, значением, ссылкой и др.).
По-видимому, все эти примеры хорошо известны. Но общая концепция
связывания способна привести и к совершенно новому понятию, отсутствующему в
традиционных ЯП.
3.3. От связывания к пакету
Из общего курса программирования известно, что такое контекст. Известно
также, что модуль - это программа, рассчитанная на многократное
использование в различных контекстах (и для этого соответствующим образом
оформленная). В традиционных ЯП контекст задается обычно совокупностью
объявлений (описаний) некоторого блока или подпрограммы и связывается с
телом блока текстуально, физическим соединением тела и контекста. Но если
модуль рассчитан на различные контексты, то и контекст, естественно, может
оказаться пригодным для работы с различными модулями. Следовательно, хорошо
бы и контекст оформлять по таким правилам, чтобы его не нужно было
выписывать каждый раз, а можно было использовать как модуль, связывая с
телом блока, например, во время трансляции. Подобной категории модулей ни в
Алголе, ни в Фортране, ни в Паскале нет. Впервые такой модуль появился в
языке Симула-67 и был назван "классом". В Аде его аналог назван "пакетом".
[Рассмотрим подробнее путь к пакету на конкретном примере.
В общем курсе программирования при изучении структур данных знакомят с
совокупностью понятий, позволяющих работать со строками. Например,
определяют представление строк одномерными массивами и предоставляют
несколько операций над строками (например, в-строку, из строки и подстрока).
Спрашивается, каким образом оформить это интеллектуальное богатство так,
чтобы им было удобно пользоваться? Алгол-60 или Паскаль позволяет записать
соответствующие объявления массивов и процедур, а тем самым сделать их
известными многим программистам. Совокупность указанных объявлений
массивов, переменных и процедур, выписанная в начале блока, позволяет в теле
блока работать в сущности на языке, расширенном по сравнению с Алголом-60
(понятием строчных переменных и набором операций над такими переменными).
Но вот мы знаем (изучили) эти объявления и хотим ими воспользоваться
(например, запрограммировать и запустить универсальный нормальный алгоритм
Маркова, как нам предлагают авторы того же курса). Алгол-60 заставляет нас
переписать в свою программу все нужные объявления. Но это и труд, и ошибки,
и время, и место на носителях. На практике, конечно, во многих реализациях
Алгола-60 есть возможность обращаться к библиотеке, где можно хранить
объявления функций и процедур (но не переменных и массивов). Однако
целостного языкового средства, обслуживающего потребность делать доступным
расширение языка, однажды спроектированное и полностью подготовленное к
использованию, нет. Другими словами, не выделена абстракция связывания
компонент потенциально полезного контекста. Нет ее ни в Паскале, ни в
Фортране, хотя общие объекты последнего - намек на движение в нужном
направлении.
Как уже сказано, впервые нужная абстракция была осознана и оформлена
соответствующим конструктом в языке Симула-67. Основная идея в том, что
совокупность объявлений можно синтаксически оформить (в качестве "класса"),
предварив их ключевым словом class и снабдив индивидуальным именем. Так
можно получить, например, класс с именем обработка_строк, в котором будут
объявлены одномерный массив и процедуры для работы с этим массивом как со
строкой символов. Чтобы воспользоваться такими объявлениями (в
совокупности!), достаточно перед началом программы указать в качестве
приставки имя нужного класса. Объявления из такого класса считаются
выписанными в фиктивном блоке, объемлющем создаваемую программу (т.е.
доступны в ней). Например, программу нормального алгоритма достаточно
предварить приставкой обработка_строк.]
В первом приближении основная идея ПАКЕТА совпадает с идеей класса -
это также совокупность объявлений, снабженная именем и пригодная для
использования в качестве "приставки". Однако в понятии "пакет" воплощены и
другие важнейшие идеи, о которых уже шла речь. Подчеркнем, что к новым
понятиям нас привела общая концепция связывания.
Вопрос. Чем идея пакета (модуля-контекста) отличается от идеи простого
копирования контекста? От идеи макроопределений?
Подсказка. Важно, когда происходит связывание, а также чего и с чем.
Кроме того, не забывайте об управлении доступом к контексту.
3.4. Связывание и специализация
Не только отдельные языковые конструкты обязаны своим возникновением
тому, что связывание было осознано как самостоятельная абстракция. На его
основе возникло целое направление в программировании - так называемое
конкретизирующее программирование (как было отмечено, связывание обобщает
основные виды конкретизации). Когда говорят о конкретизирующем
программировании, часто приводят такой пример.
Рассмотрим операцию "**" возведения основания x в степень n. Если
понятно самостоятелъное значение связывания, то легко представить себе
ситуацию, когда с операцией "**" уже связан один операнд и еще не связан
другой. С точки зрения итогового возведения в степень такая ситуация
запрещена - еще нельзя совершить запланированный акт поведения (операнды не
готовы). Но если понимать связывание как многоэтапный процесс подготовки
этого акта, то рассматриваемая ситуация может соответствовать одному из
этапов этого процесса. Более того, на аналогичном этапе связывания могут
задерживаться целые классы таких процессов. Это повторяющееся следует
выделить, обозначить и применить (пользуемся одним из важнейших общих
принципов абстрагирования - принципом обозначения повторяющегося). Так
получается целый ряд одноместных операций ("1**","2**","3**"...) при
фиксированном основании и ряд одноместных операций ("**1","**2","**3"...)
при фиксированном показателе степени. Но, например, операцию "**3" можно
реализовать просто как x*x*x, что короче, проще и эффективней общей
программы для "**".
Таким образом и возникает идея универсального конкретизатора, который
по параметрической программе (например, "**") и некоторым уже связанным с
ней аргументам строит (потенциально более эффективную) конкретизированную
программу (например, "**3"). Если такой конкретизатор удастся построить для
некоторого класса программ, то возникает надежда обеспечить целую проблемную
область эффективными и надежными, "по происхождению" правильными
программами. Ведь исходная параметрическая программа предполагается
сделанной исключительно тщательно (во всяком случае правильно) - при таком
ее широком назначении на нее не жалко усилий.
В настоящее время конкретизирующее программирование интенсивно
развивается и у нас, и за рубежом. Конкретизатор в литературе называют
иногда специализатором, а также смешанным вычислителем (за то, что он
проводит вычисления и над данными, и над программами).
Отметим, что любые языковые конструкты можно при желании считать частью
апппарата связывания. Ведь с их помощью аргументы программы связываются с ее
результатами на той или иной стадии обработки ее текста. Воспользуемся этим
наблюдением, чтобы продемонстрировать еще одно применение аппарата
связывания - уточним терминологию и укажем некоторые перспективы в теории
трансляции. Это же наблюдение положено в основу трансформационного подхода к
программированию [3].
3.3.1. Связывание и теория трансляции
Основной результат раздела: такие программы, как компилятор и
суперкомпилятор (генератор компиляторов) могут быть формально получены из
интерпретатора ЯП с помощью подходящего связывания.
Ключевая идея: следует применить особый вид связывания, обобщающий
обычный вызов функции таким образом, что часть параметров функции
оказывается связанной со своими аргументами, а остальные остаются пока
несвязанными и служат параметрами остаточной функции. Остаточной называют
функцию, вызов которой с оставшимися аргументами эквивалентен вызову
исходной функции с полным набором аргументов. Такой вид связывания называют
специализацией.
Число аргументов функции несущественно, важно лишь отделить связываемые
раньше и позже. Поэтому для уяснения основной идеи достаточно рассматривать
функции с двумя аргументами, что мы и сделаем.
Введем понятие "универсального специализатора". Вслед за Бэкусом
назовем формой функцию высшего порядка, т.е. функцию, аргумент и(или)
результат которой также представляет собой некоторую функцию. "Универсальный
специализатор" s - это форма, которая по произвольной функции двух
переменных F(X,Y) и заданному ее аргументу x0 выдает в качестве результата
функцию одного аргумента s(F,x0) такую, что для всех допустимых значений
параметра Y справедливо определяющее соотношение
(**) s(F,x0)(Y) = F(x0,Y)
так что s(F,x0) - это и есть остаточная функция после связывания
первого параметра функции F с аргументом x0.
Покажем, как получить объявленный основной результат. Допустим, что все
рассматриваемые функции и формы реализованы подходящими программами.
Сохраним для этих программ те же обозначения. Так что s(F,x0) можно теперь
считать программой, полученной по исходной программе F с помощью программы
s.
Замечание. Важно понимать, что о качестве получаемых специализированных
(остаточных) программ в определении универсального специализатора ничего не
сказано. Тривиальное преобразование программ может состоять, например, в
том, что в остаточной программе просто содержится вызов вида F(x0,Y).
Упражнение. Запишите тривиальную остаточную программу на одном из
известных вам ЯП?
Рассмотрим теперь язык программирования L и его интерпретатор i. С
одной стороны, i - это такая программа, что для всякой правильной программы
p на языке L и исходных данных d
i(p,d) = r,
где r - результат применения программы p к данным d. Другими словами,
программа i реализует семантику языка L - ставит в соответствие программе p
результат ее выполнения с данными d. С другой стороны, i - это форма от двух
аргументов (а именно так называемая ограниченная аппликация - она применяет
свой первый аргумент-функцию p ко второму аргументу d, причем пригодна
только для программ из L).
Интерпретатор может быть реализован аппаратно, т.е. быть отдельным
устройством, предназначенным для выполнения программ на L. Однако для нас