В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 70
Текст из файла (страница 70)
обойтись, достаточно вспомнить о процедуре интегрирования, зависящей от
интегрируемой функции-параметра). А в Аде такая возможность есть - и
процедуры, и типы могут служить родовыми параметрами. Другими словами,
необходимая настройка может быть выполнена при компиляции.
Еще один аргумент против динамических подпрограммных параметров в Аде -
отсутствие возможности динамически создавать подпрограммы.
Тем самым неформальная теорема "о неприемлемости подпрограммных типов в
Аде и приемлемости их в Паскале" полностью доказана.
Вопрос. Почему разговор о типах фактически оказался разговором о
динамических параметрах?
Подсказка. Типы в Аде - основное средство статического контроля.
18. Наследуемость (к идеалу развития и защиты в ЯП).
18.1. Определяющая потребность
До сих пор мы интересовались в основном созданием программ "с нуля",
почти не учитывая потребность использовать ранее предоставленные программные
услуги для создания новых. Обеспеченность потребности "развивать"
программные услуги (в ЯП, в программной среде, в средствах программирования
в целом) будем называть "развиваемостью".
Эта потребность уже оказала серьезное влияние на современное
программирование. Имеются основания считать, что в ближайшей перспективе это
влияние станет определяющим. Во всяком случае на развиваемость "работают"
уже рассмотренная нами модульность, а также стандартизация, наследование,
объектная ориентация, которые еще предстоит рассмотреть.
Другими словами, мы приступаем к обсуждению одной из фундаментальных
концепций программирования. (Не будет большого вреда, если читатель будет
иметь в виду и более широкий философский контекст.)
18.2. Идеал развиваемости
Как известно, работающая, а тем более прекрасно работающая программа -
это материализованный интеллект и немалый труд высококвалифицированных
людей, который дорого стоит и к тому же содержит в себе элемент творчества
(изобретения, открытия), результат которого в общем случае не может быть
гарантированно воспроизведен в заданных условиях и в заданные сроки при
любых мыслимых затратах. Поэтому развиваемость сознательно или интуитивно
на протяжении всей истории информатики оставалась "голубой мечтой"
создателей ЯП и иных средств программирования.
Так, еще на заре программирования появились библиотеки стандартных
программ и средства модуляризации, отразившие максимальный для того времени
уровень развиваемости. Однако в силу ряда причин, а скорее всего "по
бедности и бескультурью", доминировало стремление предоставить средства
заимствования, а не защиты от заимствования и тем более не защиты
заимствованного от разрушения. Примером такого относительно примитивного
средства может служить оператор копирования "include".
На протяжении всей книги мы говорим о таком развитии, которое
предполагает определенные гарантии защиты от разрушения созданного. Не зря в
нашей концептуальной схеме в качестве взаимно дополнительных выделены именно
средства развития и защиты абстракций, которыми мы и интересовались во всех
наших моделях ЯП. Конечно, понятие развиваемости должно включать гарантию
определенной защиты развиваемых программных услуг.
Это прежде всего защита авторского права. Она включает гарантированное
предоставление специфицированных автором услуг в специфицированной автором
среде и тем самым запрет на предоставление некоторых услуг,
несанкционированных автором. Это, конечно, и защита потребителя, уже
воспользовавшегося рассматриваемыми услугами. Его программы должны работать
без каких-либо дополнительных усилий и затрат. С другой стороны, естественно
стремиться к минимуму затрат на такое развитие программной услуги, которое
не противоречит авторскому праву и правам потребителя - так мы сформулируем
идеал развиваемости.
Примерами приближений к указанному идеалу могут служить классы и
объекты в ЯП Симула-67, подробно рассмотренные нами пакеты в Аде, модули в
Модуле-2. Как будет показано, все эти понятия не дотягивают до идеала.
Вместе с тем они послужили экспериментальным основанием для современного
воплощения идеала развиваемости.
18.2. Критичность развиваемости
Если со слабой развиваемостью еще можно было мириться в период
первоначального накопления фонда программных услуг, то в настоящее время
потребность в близкой к идеалу развиваемости приобретает характер критичной
потребности для любого современного ЯП, поскольку фонд работающих,
получивших признание программных услуг уже создан практически во всех
областях человеческой деятельности.
Более того, трудно представить себе такую область, где было бы
нерационально воспользоваться программными услугами, хорошо проявившими себя
в других областях, если при этом удается опереться на достаточно мощный
аппарат развиваемости.
18.3. Аспекты развиваемости
Выделим три аспекта общего понятия развиваемости в ЯП (и современном
программировании в целом):
модульность,
стандартизация,
наследуемость.
Модульностью мы занимались в рамках модели А. Она обеспечивает
развиваемость за счет фиксации сопряжения (интерфейса) между создателем и
потребителем услуги. В результате создатель новой услуги может
воспользоваться старой в рамках предписанного сопряжения (воспользовавшись
"модулем" с правильно оформленным сопряжением). С другой стороны, старая
услуга может быть заменена новой в рамках такого сопряжения, и пользователь
получает новый комплекс услуг при определенной гарантии работоспособности
комплекса в целом.
Итак, модульность способствует развиваемости "по частям", а тем самым
повышает избирательность заимствования и снижает его стоимость. Модули
обычно защищены от разрушения и несанкционированного использования
предоставляемых ими услуг. Вместе с тем традиционные рамки модульности
оказываются слишком жесткими, когда желательно заимствовать не весь модуль
целиком, а с предварительной корректировкой некоторых услуг.
Для тех, кто привык пользоваться текстовыми редакторами, напомним, что
желательно корректировать так, чтобы не "испортить" модуль, а этого простые
текстовые редакторы не гарантируют.
Примеры модулей неоднократно приводились - это и спецификация модуля-
пакета как сопряжение его реализации с его использованием, и спецификация
модуля-процедуры, и т.п.. Существенно, что до сих пор мы знали лишь такие
модули, которыми можно пользоваться для обслуживания объектов, заранее
неизвестных создателю модуля, но тип таких объектов всегда известен при
трансляции (чаще при написании) модуля.
Вопрос. Для каких модулей тип обрабатываемых объектов неизвестен при
трансляции.
Обратите внимание, что если статической типизации нет вообще, то трудно
говорить о каких-либо гарантиях корректности, защите авторского права и т.п.
Заметим, что статическая типизация может допускать и квазидинамический
контроль типов (как в Симуле-67 для контроля квалификаций ссылок, а в Аде -
для контроля подтипов).
Стандартизация также предполагает фиксацию сопряжения между создателем
и пользователем услуги, в частности, сопряжения модулей с контекстом, однако
основная цель при этом - не сам факт определения такого сопряжения, а
устранение нерационального разнообразия сопряжений. Характерный пример -
стандартизация ЯП: устраняется нерациональное разнообразие свойств различных
реализаций одного и того же ЯП (обычно в различных програмных средах). Таким
образом, стандартизация принципиально расширяет "рынок" готовых услуг в
пространстве и во времени, тем самым дополнительно стимулируя их
производство и снижая удельные затраты на использование - налицо обеспечение
нового уровня развиваемости. Заинтересованного читателя отсылаем к
[30,31,32].
Наследуемость - предмет настоящего раздела. Ее отличие от
стандартизации ЯП в том, что она не выходит за рамки одной программной
среды. От традиционной модульности она отличается тем, что подразумевает
существенно более гибкий аппарат заимствования, развития и защиты,
действующий на уровне практически произвольных языковых объектов, а не
только на уровне заранее предусмотренных модулей.
Такой уровень гибкости позволяет, в частности, легко приспособить
программу к обслуживанию объектов, тип которых неизвестен не только при ее
создании, но и при трансляции (с гарантией статического контроля типов).
Как мы увидим, это исключительно интересная концепция, одна из
"изюминок" современного программирования, значительное приближение к идеалу
развиваемости. Важно понимать, что этот аспект развиваемости ориентирован
прежде всего на программистов, т.е. создателей новых программных услуг (в
отличие от стандартизации и, частично, модульности, которые ориентированы и
на конечных пользователей непосредственно).
Примером продвижения в обозначенном направлении может служить
наследуемость операций производных типов в Аде, однако, как будет показано,
она обладает существенными недостатками. Поразительна наследуемость в
Симуле-67, в которой еще двадцать лет назад оказалось почти в точности то,
что лишь сейчас осознается как самое главное в программировании
(естественно, после принципиальной возможности программировать). Ближе к
современному идеалу наследуемость в Обероне и Турбо Паскале 5.5 (а также в
Смолтоке-5 и Си++) - по сравнению с Симулой-67 прежде всего за счет
эффективности и защиты.
Итак, модульность обеспечивает "упаковку" программных услуг в модули-
контейнеры, стандартизация - доставку упакованных услуг потребителю-
программисту в работоспособном состоянии, а наследуемость - изготовление
контейнера новых услуг с минимальными затратами, минимальным риском и в
рамках законности.
Конечно, каждый из трех названных аспектов опирается на тот или иной
вариант абстракции-конкретизации. Читателю предлагается самостоятельно их
проанализировать (по прочтении соответствующих разделов).
Подчеркнем, что применяемая терминология - не общепринята. Однако нам
представляется, что, с одной стороны, наши термины достаточно точно отражают
суть дела (с учетом смысла применяемых слов в русском языке), а, с другой
стороны, за обсуждаемыми понятиями термины еще не закреплены (во всяком
случае, в литературе на русском языке).
18.4. Идеал наследуемости (основные требования)
Содержательно идеал наследуемости звучит так : программировать только
принципиально новое. Уточним его с учетом типизации языковых объектов.
Итак, в идеале должно быть возможно:
определять новый тип, наследующий те и только те атрибуты исходного
типа, которые желательны.
пополнять перечень атрибутов нового типа по сравнению с перечнем
атрибутов объектов исходного типа.
гарантировать применимость сохраняемых операций исходного типа к
объектам нового типа.
Вопрос. Уместна ли здесь типизация?
Подсказка. Очень редко нужны программы, делающие абсолютно одно и то
же. (Кстати, знаете ли Вы примеры таких программ?)
18.5. Проблема дополнительных атрибутов
Вернемся к принципу защиты авторского права, рассмотренному в связи с
раздельной коипиляцией. Суть его в том, что ЯП должен предоставлять
программисту возможность поставлять программный продукт, пригодный для
санкционированного использования, но защищенный от использования
несанкционированного (в частности, от переделки программ или такого их
"развития", при котором возникает опасность, что ранее работавшие программы
работать перестанут). В Аде этот принцип поддержан отделением спецификации
от реализации и возможностью не поставлять покупателю исходные тексты
реализаций (тел пакетов, подпрограмм и задач).
Однако абсолютно запретить развитие программ неразумно. В Аде, в
частности, имеются богатые средства развития (подпрограммы и определяемые
типы). К сожалению, нетрудно привести примеры, когда эти средства
оказываются недостаточными, если нежелательно или невозможно менять тексты
реализаций. Подчеркнем, что такая ситуация возникает не только из-за
недоступности текстов реализаций, но и из-за риска внести ошибку при их
переделке.
Постановка задачи.
Попытаемся, например, развить услуги пакета "управление_сетями" для
сетей, обогащенных дополнительными атрибутами. Пусть, для определенности
каждый узел обладает некоторым "весом", содержательно соответствующим числу
жителей в городе, представленном этим узлом.